Давайте пройдем через это!
keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = _
Так что мы хотим keepValue
делать? Ну, первое, что мы должны сделать, это использовать ma
, поэтому мы можем подключить его к f
.
keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = do
a <- ma
_
Теперь мы имеем значение va
типа a
, поэтому мы можем передать его f
.
keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = do
va <- ma
vb <- f va
_
И, наконец, мы хотим производить va
, так что мы можем просто сделать:
keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = do
va <- ma
vb <- f va
return va
Это как бы я пройти через написание первого проекта любого монадической функции, как это. Тогда я почистил бы его. Во-первых, некоторые мелочи: с Applicative
является суперклассом Monad
, я предпочитаю pure
по return
; мы не использовали vb
; и я бы сбросил v
в именах. Так на основе версии с do
-notation этой функции, я думаю, что лучший вариант
keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = do
a <- ma
_ <- f a
pure a
Теперь, однако, мы можем начать, чтобы сделать реализацию лучше. Во-первых, мы можем заменить _ <- f va
с явным вызовом (>>)
:
keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = do
a <- ma
f a >> pure a
И теперь мы можем применить упрощение. Вы знаете, что мы всегда можем заменить (>>=)
плюс pure
/return
с fmap
/(<$>)
: любой из pure . f =<< ma
, ma >>= pure . f
или do a <- ma ; pure $ f a
(все из которых эквивалентны) могут быть заменены f <$> ma
. Тем не менее, Functor
класса типа имеет другой, менее известный метод, (<$)
:
(<$) :: a -> f b -> f a
заменить все места в входе с тем же значением. По умолчанию используется значение fmap . const
, но это может быть отменено более эффективной версией.
Таким образом, мы имеем аналогичное правило замены для (<$)
: мы всегда можем заменить ma >> pure b
или do ma ; pure b
с b <$ ma
. Это дает нам
keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = do
a <- ma
a <$ f a
И я думаю, что это самая короткая разумная версия этой функции! Нет никаких хороших точных трюков, чтобы сделать это чище; одним из индикаторов является множественное использование a
во второй строке блока do
.
Кстати, терминология примечание: вы работаете два монадическое действие или два монадического значение; вы не работаете * «две монады». Монада - это что-то вроде Maybe
- конструктор типа, который поддерживает (>>=)
и return
. Не смешивайте значения с типами - такое терминологическое различие помогает держать вещи яснее!
В общем, вы не можете ничего сделать, чтобы «выбросить» эффект монадического действия. Если вы действительно хотите сохранить оба эффекта, то ваша функция будет '\ x f -> x >> = \ a -> f a >> return a'. – user2407038
В самом общем случае * результат *, заданный монадической функцией, зависит от его * efect * (или его тайны), поэтому я не думаю, что вы можете просто избежать эффекта некоторой избранной монадической функции. (Я думаю об IO ... что, если 'ma' берет' a' со стандартного ввода?) – Euge