В отчете Haskell за 2010 г. говорится
A do expression provides a more conventional syntax for monadic programming. It allows an expression such as
putStr "x: " >> getLine >>= \l -> return (words l)
to be written in a more traditional way as:
do putStr "x: " l <- getLine return (words l)
Haskell the Craft of Functional Programming от Томпсона говорит
We'll continue to use the do notation, but will keep in mind that it essentially boils down to the existence of a function (>>=) which does the work of sequencing I/O programs, and binding their results for future use.
Означает ли вышеизложенное, что нотация do обязательно используется в контексте монады?
Если да, то почему следующий функтор использует нотацию do?
instance Functor IO where
-- fmap :: (a -> b) -> IO a -> IO b
fmap g mx = do {x <- mx; return (g x)}
Ну, есть также ApplicativeDo
, расширение GHC, которое позволяет использовать нотацию do с аппликативами. Я никогда не пытался использовать его сам, но я уверен, что где-то читал, что он может быть официально добавлен в язык всякий раз, когда они готовят следующий отчет.
@RobinZigmond, надеюсь, что нет. Это не самое лучшее из расширений. Но, возможно, его можно очистить.
Да. Как говорится в цитируемой статье, do
-обозначение простосинтаксический сахардля монадных операций.
Это правила для десахаризации do
-нотации:
do {foobar; ...} = foobar >> do {...}
(он же foobar >>= \_ -> do {...}
)do {a <- foobar; ...} = foobar >>= \a -> do {...}
do {foobar} = foobar
Это обязательно означает, что do
-нотация работает исключительно с монадами, за исключением тривиального случая, который описывает правило 3.
Так, например, как говорится в статье, do {putStr "x: "; l <- getLine; return (words l)}
в точности равно putStr "x: " >> (getLine >>= \l -> return (words l))
, что вы можете подтвердить с помощью правил удаления сахара.
В приведенном выше определении для Functor IO
экземпляр Monad IO
уже определен, поэтому мы также используем его для определения экземпляра Functor
.
Также может быть полезно отметить, что все монады обязательно являются функторами (см. определение класса типов Monad
), поэтому, когда кто-то говорит, что do
-нотация работает с монадами, она также обязательно работает и с функторами. Я подозреваю, что это может быть пунктом путаницы.
Стоит отметить, что в некоторых ограниченных случаях можно использовать только Applicative
операции, а не более общие Monad
операции. Например, пример, приведенный в статье, может быть записан как putStr "x: " *> (pure words <*> getLine)
. Существует экспериментальное расширение языка под названием ApplicativeDo
, которое добавляет в GHC возможность распознавать такие ситуации и обобщать определенные случаи do
-нотации на все аппликативы, а не только на все монады.
Спасибо. Откуда вы знаете, что «в определении Functor IO, которое вы цитируете выше, экземпляр Monad IO уже определен», т.е. что определено перед чем?
@ Тим, я знаю это целостно, потому что они используют do
-нотацию для определения fmap
! Однако, как правило, в Haskell невозможно измерить порядок определения, поскольку возможна взаимная рекурсия. Скорее, я имею в виду, что экземпляр Monad определен и не относится к fmap
рекурсивно. Я могу узнать это, посмотрев определение на гугл.
Посмотрите на правило 3 немного внимательнее. Это чисто синтаксическое обезуглероживание. do ()
является действительным, а не монадическим. Это просто не очень полезно.
@Carl Спасибо, что указали на этот пограничный случай. Я добавил немного об этом. На самом деле мне пришлось проверить с помощью GHCI, что do ()
действительно действителен!
@AJFarmar Кроме того, если вы делаете do { one_expr }
, это тоже не монадично. Это может показаться крайним случаем, но я видел предложение использовать его, когда $
не совсем правильно, например. делаю (,) <$> do long_exp_number_1 {-newline-} <*> do long_exp_number_2
. Я дам ссылку на него, если найду оригинальный совет.
Что ж, с расширением Синтаксис Rebindable он будет использовать любые (>>=)
, (>>)
и fail
, которые находятся в области видимости. Так что технически Monad
в таком случае не требуется, и вы можете сделать что-то глупое, например:
{-# LANGUAGE RebindableSyntax #-}
a >>= b = b a
return a = a
-- test == ("foo","boo")
test = do
a <- "foo"
b <- "boo"
return (a,b)
Да.
do
нотация, как уже упоминалось, — это просто синтаксический сахар для монадных операций. В вашем примере с экземпляромFunctor
дляIO
мы уже определили монадные операции надIO
и используем их для определенияfmap
.