在 monad 的上下文中是否一定要使用 do 符号?

Is do notation used necessarily in the context of monad?

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 Thompson 的函数式编程技巧说

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.

以上是否意味着在 monad 上下文中必须使用 do 符号?

如果是,为什么下面的仿函数使用do表示法?

instance    Functor IO  where
    --  fmap    ::  (a  ->  b)  ->  IO  a   ->  IO  b
    fmap    g   mx  =   do  {x  <-  mx; return  (g  x)}

是的。正如引用的文章所述,do-notation 只是 语法糖 用于 monad 操作。

这些是 the rules 用于脱糖的 do-notation:

  1. do {foobar; ...} = foobar >> do {...}(又名 foobar >>= \_ -> do {...}
  2. do {a <- foobar; ...} = foobar >>= \a -> do {...}
  3. do {foobar} = foobar

这必然意味着 do-notation 完全适用于 monad,除了规则 3 描述的微不足道的情况。

因此,例如,如文章所述,do {putStr "x: "; l <- getLine; return (words l)} 正好等于 putStr "x: " >> (getLine >>= \l -> return (words l)),您可以通过脱糖规则来确认。

在您上面引用的 Functor IO 的定义中,已经定义了 Monad IO 实例,因此我们也使用它来定义 Functor 实例。

注意到所有 monads 都必然是函子可能也很有用(参见 definition of the Monad typeclass),所以当有人说 do-notation 对 monads 起作用时,它也必然对函子起作用以及。我怀疑这可能是一个混淆点。


值得注意的是,在某些受限制的情况下,可以仅使用 Applicative 操作而不是更通用的 Monad 操作。比如文章提供的例子可以写成putStr "x: " *> (pure words <*> getLine)。有一个名为 ApplicativeDo 的实验性语言扩展,它增加了 GHC 识别这些情况的能力,并将 do-notation 的某些情况推广到所有应用程序,而不仅仅是所有 monad。

好吧,有了 RebindableSyntax 扩展,它将使用范围内的任何 (>>=)(>>)fail。所以技术上 Monad 在这种情况下不是必需的,你可以做一些愚蠢的事情:

{-# LANGUAGE RebindableSyntax #-}

a >>= b = b a
return a = a

-- test == ("foo","boo")
test = do
  a <- "foo"
  b <- "boo"
  return (a,b)