Нотация Do-обозначений синтаксисом (>> =) выполняется следующим образом:
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 или нет влияет на компоновку;это строго вопрос синтаксиса.
Ваша другая функция похожа:
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, но есть и другие варианты.Трансформаторы монад позволяют создавать новую монаду с комбинированным поведением основных монад.В этом случае вы используете функции типа 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
, то есть IO String
.Это поднято в монаду MaybeT с помощью функции lift
.
Редактировать Комментарий 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
класса *1052*.В нотации do, когда не удается сопоставить шаблон, вызывается 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
s полезность зависит от монады.Для списков это невероятно полезно, в основном заставляя сопоставление с образцом работать в списках.Это также очень хорошо в монаде Maybe
, поскольку ошибка сопоставления с образцом может привести к неудачному вычислению, то есть именно тогда, когда Maybe
должно быть Nothing
.Для IO
, возможно, не так много, поскольку он просто генерирует исключение пользовательской ошибки через error
.
Вот и вся история.