Что похоже на fmap для монадических значений?

Это должно быть легко для профессионалов Haskell.

У меня есть значение Maybe,

> let a = Just 5

Я могу напечатать его:

> print a
Just 5

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

> maybe (return ()) print a
5

Однако это кажется слишком многословным. Прежде всего, return () специфичен для монады ввода-вывода, поэтому мне нужно придумать другой "ноль" для каждой монады, в которой я хочу попробовать этот трюк.

Я хочу в основном отобразить действие ввода-вывода (печать) на значение Maybe и напечатать его, если оно Just, или ничего не делать, если оно Nothing. Я хочу как-то выразить это,

> fmap print a

Но это не работает, поскольку print является действием IO:

No instance for (Show (IO ()))

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

> print <$> a
No instance for (Show (IO ()))

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

Спасибо.

Ответ 1

Ответ на пелотум - простой. Но не самое интересное! sequence - это функция Haskell, о которой можно подумать как о переводе порядка конструкторов типов между списком и монадой.

sequence :: (Monad m) => [m a] -> m [a]

Теперь вам нужно, так сказать, перевернуть порядок конструкторов типов между a Maybe и монадой. Data.Traversable экспортирует функцию sequence с такой емкостью!

Data.Traversable.sequence :: (Traversable t, Monad m) => t (m a) -> m (t a)

Это может специализироваться на Maybe (IO ()) -> IO (Maybe ()), как в вашем примере.

Следовательно:

Prelude Data.Traversable> Data.Traversable.sequence (fmap print $ Nothing)
Nothing

Prelude Data.Traversable> Data.Traversable.sequence (fmap print $ Just 123)
123
Just ()

Обратите внимание, что существует также функция sequenceA, которая немного более общая, работает не только на Monads, но и на всех Applicatives.

Так зачем использовать этот подход? Для Maybe подход, который разоблачает его явно, является точным. Но как насчет более крупной структуры данных - например, Map? В этом случае traverse, sequenceA и друзья из Data.Traversable могут быть действительно удобными.

Изменить: как отмечает Эдька, traverse :: Applicative f => (a -> f b) -> t a -> f (t b) и поэтому можно просто написать traverse print $ Just 123.

Ответ 2

Прежде всего, return() специфичен для монады ввода-вывода, поэтому мне нужно придумать другой "нуль" для каждой монады, в которой я хочу попробовать этот трюк.

return () на самом деле довольно общий, что видно по его типу:

Prelude> :t return ()
return () :: (Monad m) => m ()

Я не вижу ничего плохого в подходе maybe (return ()) print a.

Ответ 3

Но я хочу применить действие ввода-вывода к внутренней части Maybe.

Это может быть достигнуто с помощью монадных трансформаторов.

MaybeT - монада, которая может быть обернута вокруг другой монады. Другими словами, MaybeT может использовать любую другую Monad для абстрагирования провала (невиновного [1]) при вычислении.

К сожалению, GHCi не (в 2011 году) не имеет какой-либо функциональности, чтобы упростить игру с монадными трансформаторами, но здесь вы идете:

> :m + Control.Monad.Maybe Control.Monad.Trans
> let a = Just 5
> runMaybeT$ do { v <- MaybeT$ return a ; liftIO$ print v }
5
Just ()

Для более глубокого понимания монадов и монадных трансформаторов, я предлагаю вам прочитать другие источники в Интернете. Имейте в виду, что монады также просто переносят значения.

Я постараюсь, чтобы все было просто. Подписи: m = IO, a = Integer

runMaybeT:: MaybeT m a → m (Возможно, a) - превращает вычисление в MaybeT IO в вычисление в IO.

do { - используйте обозначения без отступов, чтобы они соответствовали запросу ghci [2].

MaybeT:: m (Возможно, a) → MaybeT m a - Завершить вычисление типа IO (возможно, Integer).

return a:: IO (Just Integer) - замените это на ваши вычисления.

lift. Запустите вычисление в завернутой монаде. [3]

Just() - результат вычисления. GHCi печатает результаты ввода-вывода, когда это не().

MaybeT не включен в mtl, поэтому вам может потребоваться установить его

cabal install MaybeT

Или рассмотрим [1]


[1] Для передачи сообщений об ошибках используйте MonadError

[2] Я знаю о многострочном вводе в GHCi

[3] Используйте liftIO, если вам нужно IO из стека монадов.

Ответ 4

Вы пробовали это?

unwrap :: (Show a) => Maybe a -> IO ()
unwrap Nothing  = return ()
unwrap (Just a) = print a

Он вернет/распечатает представленные данные после их разворачивания.