Ответ 1
Условные обозначения Do-notation для ( → =) синтаксиса следующим образом:
getPerson = do
name <- getLine -- step 1
age <- getInt -- step 2
return $ Just Person <*> Just name <*> age
getPerson2 =
getLine >>=
( \name -> getInt >>=
( \age -> return $ Just Person <*> Just name <*> age ))
каждая строка в do-notation после первого преобразуется в лямбда, которая затем привязана к предыдущей строке. Это полностью механический процесс привязки значений к именам. Я не вижу, как использование do-notation или не повлияло бы на композицию вообще; это строго вопрос синтаксиса.
Другая функция аналогична:
getInt :: IO (Maybe Int)
getInt = do
n <- fmap reads getLine :: IO [(Int,String)]
case n of
((x,""):[]) -> return (Just x)
_ -> return Nothing
getInt2 :: IO (Maybe Int)
getInt2 =
(fmap reads getLine :: IO [(Int,String)]) >>=
\n -> case n of
((x,""):[]) -> return (Just x)
_ -> return Nothing
Несколько указателей на направление, в котором вы, кажется, направляетесь:
При использовании Control.Applicative
часто полезно использовать <$>
для подъема чистых функций в монаду. В этой строке есть хорошая возможность:
Just Person <*> Just name <*> age
становится
Person <$> Just name <*> age
Кроме того, вы должны изучить трансформаторы монады. Пакет mtl наиболее распространен, потому что он поставляется с платформой Haskell, но есть и другие варианты. Трансформаторы Monad позволяют создавать новую монаду с комбинированным поведением лежащих в основе монадов. В этом случае вы используете функции с типом IO (Maybe a)
. Mtl (фактически базовая библиотека, трансформаторы) определяет
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
Это то же самое, что и тип, который вы используете, с переменной m
, созданной в IO
. Это означает, что вы можете написать:
getPerson3 :: MaybeT IO Person
getPerson3 = Person <$> lift getLine <*> getInt3
getInt3 :: MaybeT IO Int
getInt3 = MaybeT $ do
n <- fmap reads getLine :: IO [(Int,String)]
case n of
((x,""):[]) -> return (Just x)
_ -> return Nothing
getInt3
является точно таким же, за исключением конструктора MaybeT
. В принципе, в любое время, когда у вас есть m (Maybe a)
, вы можете обернуть его в MaybeT
, чтобы создать MaybeT m a
. Это обеспечивает более простую компоновку, как вы можете видеть по новому определению getPerson3
. Эта функция не беспокоится о сбое вообще, потому что все это обрабатывается сантехникой MaybeT. Один оставшийся кусок getLine
, который является просто a IO String
. Это поднимается в монаду MaybeT с помощью функции lift
.
Edit
Комментарий newacct предполагает, что я должен предоставить пример соответствия шаблону; это в основном то же самое с одним важным исключением. Рассмотрим этот пример (монада монады - это монада, нас интересует, Maybe
только для соответствия шаблону):
f :: Num b => [Maybe b] -> [b]
f x = do
Just n <- x
[n+1]
-- first attempt at desugaring f
g :: Num b => [Maybe b] -> [b]
g x = x >>= \(Just n) -> [n+1]
Здесь g
выполняет то же самое, что и f
, но что, если совпадение шаблона терпит неудачу?
Prelude> f [Nothing]
[]
Prelude> g [Nothing]
*** Exception: <interactive>:1:17-34: Non-exhaustive patterns in lambda
Что происходит? Этот конкретный случай является причиной одной из самых больших бородавок (IMO) в Haskell, методе Monad
class fail
. В do-notation, когда совпадение шаблона терпит неудачу fail
. Фактический перевод будет ближе к:
g' :: Num b => [Maybe b] -> [b]
g' x = x >>= \x' -> case x' of
Just n -> [n+1]
_ -> fail "pattern match exception"
теперь имеем
Prelude> g' [Nothing]
[]
fail
Полезность зависит от монады. Для списков это невероятно полезно, в основном, работа с шаблоном работает в понимании списков. Он также очень хорош в монаде Maybe
, так как ошибка совпадения с образцом приведет к неудачному вычислению, что точно, когда Maybe
должно быть Nothing
. Для IO
, возможно, не так много, поскольку он просто выдает исключение ошибки пользователя через error
.
Это полная история.