Добавить действие без изменения результата в нотацию рефакторинга

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

ask = do
  result <- getLine
  putStrLn result
  return result

Я надеялся написать это немного более понятным и понятным, поэтому я попробовал это:

ask' = getLine <* putStrLn

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


person amoebe    schedule 08.12.2015    source источник
comment
Итак, вы хотите getLine >>= \x -> putStrLn x >> return x в безточечном стиле. Вы просили ламбдабота? Там написано liftM2 (>>) putStrLn return =<< getLine.   -  person Thomas M. DuBuisson    schedule 08.12.2015
comment
@ ThomasM.DuBuisson Похоже, Lambdabot нашел почти ту функцию, которую хотел OP! flip (liftM2 (>>)) :: Monad m => (a -> m b) -> (a -> m c) -> (a -> m b) - фактический тип немного более обобщен, поэтому его трудно увидеть.   -  person user2407038    schedule 08.12.2015
comment
@ ThomasM.DuBuisson Спасибо, я попросил pointfree инструмент командной строки, и он не смог обработать do, поэтому я сдался. В итоге я использовал ask = getLine >>= liftM2 (>>) putStrLn return, что выглядит нормально, еще раз спасибо! Вы можете сформулировать это как ответ, если хотите, тогда я могу отметить его как решенное.   -  person amoebe    schedule 08.12.2015


Ответы (3)


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

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

import Prelude hiding (id)
import Control.Arrow

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

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

person leftaroundabout    schedule 08.12.2015
comment
Вы правы, наверное, это не очень хорошая идея. Стрелки выглядят действительно сложно. Я не уверен, что решение с liftM2 достаточно ясное или запутанное? - person amoebe; 08.12.2015
comment
Проблема с liftM2(>>) в том, что он смешивает две разные монады ((a->) и IO) довольно запутанным способом. Если вы делаете что-то подобное, лучше сделайте это правильно с ReaderT, но это того стоит, только если вы читаете одно и то же значение из целой кучи мест. - person leftaroundabout; 09.12.2015

Я хочу последовательно скомпоновать два монадных действия в 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
person Luis Casillas    schedule 09.12.2015

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

bracket getLine putStrLn return

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

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

См. Также Следует ли избегать использования do-notation в Haskell?

person Petr    schedule 09.12.2015