Ответ 1
Чтобы ответить на вопрос о разнице между Writer w (Maybe a)
vs MaybeT (Writer w) a
, давайте начнем с определения:
newtype WriterT w m a = WriterT { runWriterT :: m (a, w) }
type Writer w = WriterT w Identity
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
Используя ~~
, чтобы означать "структурно подобный", мы имеем:
Writer w (Maybe a) == WriterT w Identity (Maybe a)
~~ Identity (Maybe a, w)
~~ (Maybe a, w)
MaybeT (Writer w) a ~~ (Writer w) (Maybe a)
== Writer w (Maybe a)
... same derivation as above ...
~~ (Maybe a, w)
Итак, в некотором смысле вы правы - структурно и Writer w (Maybe a)
, и MaybeT (Writer w) a
одинаковы - оба являются по существу просто парой значения Maybe и w
.
Различие заключается в том, как мы рассматриваем их как монадические значения.
Функции класса return
и >>=
выполняют разные вещи в зависимости от
на которых монада они являются частью.
Рассмотрим пару (Just 3, []::[String])
. Используя ассоциацию
мы здесь вывели здесь, как эта пара будет выражена в обеих монадах:
three_W :: Writer String (Maybe Int)
three_W = return (Just 3)
three_M :: MaybeT (Writer String) Int
three_M = return 3
И вот как мы построим пару (Nothing, [])
:
nutin_W :: Writer String (Maybe Int)
nutin_W = return Nothing
nutin_M :: MaybeT (Writer String) Int
nutin_M = MaybeT (return Nothing) -- could also use mzero
Теперь рассмотрим эту функцию для пар:
add1 :: (Maybe Int, String) -> (Maybe Int, String)
add1 (Nothing, w) = (Nothing w)
add1 (Just x, w) = (Just (x+1), w)
и посмотрим, как мы будем реализовывать его в двух разных монадах:
add1_W :: Writer String (Maybe Int) -> Writer String (Maybe Int)
add1_W e = do x <- e
case x of
Nothing -> return Nothing
Just y -> return (Just (y+1))
add1_M :: MaybeT (Writer String) Int -> MaybeT (Writer String) Int
add1_M e = do x <- e; return (e+1)
-- also could use: fmap (+1) e
В общем, вы увидите, что код в монаде MaybeT более краткий.
Кроме того, семантически две монады очень разные...
MaybeT (Writer w) a
- это действие Writer, которое может выйти из строя, а сбой -
автоматически обрабатывается для вас. Writer w (Maybe a)
- это просто писатель
действие, которое возвращает Maybe. Ничего особенного не происходит, если это значение Maybe
оказывается ничто. Это проиллюстрировано в функции add1_W
, где
нам пришлось выполнить анализ случая на x
.
Еще одна причина, чтобы предпочесть подход MaybeT
заключается в том, что мы можем написать код
который является общим для любого стека монады. Например, функция:
square x = do tell ("computing the square of " ++ show x)
return (x*x)
может использоваться без изменений в любом стеке монады, который имеет строку Writer, например:
WriterT String IO
ReaderT (WriterT String Maybe)
MaybeT (Writer String)
StateT (WriterT String (ReaderT Char IO))
...
Но возвращаемое значение square
не устанавливает проверку с Writer String (Maybe Int)
, потому что square
не возвращает Maybe
.
Когда вы вводите код Writer String (Maybe Int)
, вы явно указываете код
структура монады делает его менее общим. Это определение add1_W
:
add1_W e = do x <- e
return $ do
y <- x
return $ y + 1
работает только в двухслойном стеке монады, тогда как функция типа square
работает в гораздо более общей обстановке.