2011-02-07 2 views
10

Это должно быть легко для Haskell профи ..Что похоже на fmap для монадических значений?

У меня Maybe значение,

> let a = Just 5 

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

> print a 
Just 5 

Но я хочу, чтобы применить I/O действия внутри «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())) 

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

Спасибо.

+2

Btw 'print <$> a' точно такой же, как' fmap print a', –

ответ

23

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

sequence :: (Monad m) => [m a] -> m [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 функция, которая является немного более общем, работает не только на монады, но все аппликативных.

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

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

+0

+1 для веселого ответа :) –

+9

Вы можете просто использовать 'traverse print $ Just 123' с тем же result –

+0

Источник дает простое определение для траверсы: 'traverse f = sequenceA. fmap f' –

16

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

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

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

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

0

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

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

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

+0

Какой модуль находится в? – Steve

+0

@Steve - он определяет свою собственную функцию 'unwrap' –

+0

Кстати, это напечатает«() »в случае, когда аргумент« Ничего »... Я думаю, что намерение OP состояло в том, чтобы ничего не делать в этом случае –

2

Но я хочу применить действие ввода-вывода к внутренней части 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() 

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

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

runMaybeT :: MaybeT м а -> м (Может быть) - Включает вычисление в MaybeT IO в расчете на IO.

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

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

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

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

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

MaybeT не входит в MTL, так что вы, возможно, придется установить его

cabal install MaybeT 

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


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

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

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

+0

Спасибо! Я действительно хочу больше узнать о трансформаторах. Я уже много читал, но все еще испытываю трудности с пониманием/знанием _when_, чтобы использовать их. Думаю, я узнаю, что я просто использую их чаще, поэтому, возможно, это хорошая возможность. – Steve

Смежные вопросы