Что похоже на 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
Он вернет/распечатает представленные данные после их разворачивания.