2015-12-08 4 views
7

Я хочу последовательно составлять два действия монады в Haskell, отбрасывая любое значение, созданное вторым, и передавая аргумент обоим действиям. В настоящее время я использую Do-блок, как это:Добавить действие без изменения результата в рефакторинг do-notation

ask = do 
    result <- getLine 
    putStrLn result 
    return result 

Я надеялся написать эту немного больше безточечный и аккуратно, поэтому я попытался это:

ask' = getLine <* putStrLn 

Однако это Безразлично» t даже проверить тип, и проблема в том, что <* не передает результат первого действия ко второму. Я хочу связать действия, например, >>=, но не изменить результат. Тип должен быть (a -> m b) -> (a -> m c) -> (a -> m b), но Hoogle не дает подходящих результатов. Что было бы оператором для достижения этой функции?

+1

Так что вы хотите 'GetLine >> = \ х -> putStrLn х >> вернуться x' в безточечном. Вы спрашивали lambdabot? Он говорит 'liftM2 (>>) putStrLn return = << getLine'. –

+0

@ ThomasM.DuBuisson Кажется, что Lambdabot нашел почти ту точную функцию, которую хотел OP! 'flip (liftM2 (>>)) :: Monad m => (a -> mb) -> (a -> mc) -> (a -> mb)' - фактический тип немного более обобщен, поэтому он несколько трудно видеть. – user2407038

+0

@ ThomasM.DuBuisson Спасибо, я попросил инструмент командной строки 'pointfree', и он не мог обработать' do', поэтому я сдался. Я закончил использовать 'ask = getLine >> = liftM2 (>>) putStrLn return', который выглядит хорошо, спасибо снова! Вы можете поместить это в ответ, если хотите, тогда я могу отметить его как решение. – amoebe

ответ

8

В тенденции, если вы используете одно значение в двух разных местах, это, вероятно, это хорошая идея, чтобы дать ему имя в ясном do блоке, а не нажимать на бессмысленный стиль.

Абстрактная концепция разделения потока информации на различные действия зафиксирована в cartesian monoidal categories, известном Haskellers как arrows. В вашем случае, вы в основном работаете в категории IO Клейли:

import Prelude hiding (id) 
import Control.Arrow 

ask' :: Kleisli IO() String 
ask' = Kleisli (\()->getLine) >>> (putStrLn &&& id) >>> arr snd 

Я не думаю, что это хорошая идея, чтобы написать такой код.

+0

Вы правы, это, вероятно, не очень хорошая идея. Стрелки выглядят очень сложными. Я не уверен, что решение с 'liftM2' достаточно ясно или сбивает с толку? – amoebe

+0

Проблема с 'liftM2 (>>)' заключается в том, что она смешивает две разные монады ('(a ->)' и 'IO') в действительно запутанном виде. Если вы сделаете что-то подобное, лучше сделайте это правильно с помощью 'ReaderT', но это стоит того, если вы читаете одно и то же значение из целого ряда мест. – leftaroundabout

2

Я хочу последовательно составлять два действия монады в Haskell, отбрасывая любое значение, полученное вторым, и передавая аргумент обоим действиям.

Это звучит для меня как Reader -функции типа r -> m a изоморфен ReaderT r m a, и монада работает неявно затыкать в том же r значения во все «дырку». Так, например:

import Control.Applicative 
import Control.Monad.Reader 

example :: IO String 
example = getLine >>= discarding putStrLn 

discarding :: Monad m => (a -> m b) -> a -> m a 
discarding action = runReaderT (ReaderT action *> ask) 

Оператор вы хотите, то что-то вроде:

action `thingy` extra = action >>= discarding extra 

Но конечно discarding имеет более простую реализацию:

discarding :: Applicative f => (a -> f b) -> a -> f a 
discarding action a = action a *> return a 

... так в конце концов, Я думаю, что это действительно кодовый гольф. Но в более сложной программе, где это общая картина в более широком масштабе, это может стоить того.В принципе, если у вас есть:

a0 :: r -> m a0 
a1 :: r -> m a1 
    . 
    . 
    . 
an :: r -> m an 

Отсюда следует, что:

ReaderT a0 :: ReaderT r m a0 
ReaderT a1 :: ReaderT r m a1 
    . 
    . 
    . 
ReaderT an :: ReaderT r m an 

И потом:

runReaderT (ReaderT a0 <* ReaderT a1 <* ... <* ReaderT an) :: r -> m a0 
2

Для полноты, в данном конкретном случае (IO) монады, вы могли бы также злоупотребление bracket для этой цели:

bracket getLine putStrLn return 

Но я сильно отговариваю его, так как это будет гораздо менее читаемым, чем исходный блок-блок, это просто уродливо.

Как уже упоминалось, в этом конкретном случае наименование результата представляется наилучшим способом.

Смотрите также Should do-notation be avoided in Haskell?