Skip to content
Go back

do语法

Updated:

do语法

因为 I/O 是如此危险且不可预测,所以当获得来自 I/O 的值后,Haskell 不允许在 IO 类型上下文之外使用该值。无法逃脱 IO 上下文意味着您需要一种方便的方法来在 IO 上下文中执行一系列计算。这就是特殊 do 关键字的目的。这种 do 表示法允许将 IO 类型视为常规类型。这也解释了为什么有些变量使用let而另一些变量使用<-。使用 <- 分配的变量允许将 IO a 类型视为 a 类型。每当创建非 IO 类型的变量时,都可以使用 let 语句。

do 语法在 Haskell 中用来简化操作多个 Monad 时的代码。每个 Monad 都实现了两个基本操作:

  1. >>= (bind):将一个 Monad 的值传递给下一个操作。即“绑定”操作符,用来把一个 Monad 中的值交给另一个函数。
  2. return:将一个普通的值放入 Monad 中,使它成为 Monad 的一部分。

但直接用这些操作符来写代码可能会显得复杂和难以阅读,特别是在需要处理多步操作的情况下。这时,do 语法就能使代码更清晰和简洁。

do 语法结构

do 块中,你可以按顺序写出一系列操作,每个操作可以是一个 Monad 操作,或者使用 <- 从 Monad 中提取值。

do 语法基本结构


do
    action1
    action2
    ...
    actionN

每个 action 都是一个 Monad 操作,执行顺序从上到下。

do 语法中的 <-

使用 do 的场景

1. I/O 操作中的 do 语法

在 I/O 操作中,do 语法被广泛使用。以下是一个经典的例子:

main :: IO ()
main = do
    putStrLn "What is your name?"
    name <- getLine
    putStrLn ("Hello, " ++ name ++ "!")

在这个例子中,do 块使 I/O 操作看起来像是一个命令式语言的顺序操作:

实际上,这背后发生的是每一步都是 IO Monad 的操作,do 语法帮我们简化了使用 >>= 操作符的过程。

等效于用 >>= 操作符来写的话,代码会变得不那么直观:


main :: IO ()
main = putStrLn "What is your name?" >>= \_ ->
       getLine >>= \name ->
       putStrLn ("Hello, " ++ name ++ "!")

通过 do 语法,这样的操作变得更加简洁且易于理解。

2. 在 Maybe Monad 中使用 do

Maybe Monad 表示可能有值(Just)或无值(Nothing),我们可以通过 do 语法来处理可能的失败情况。

findUser :: String -> Maybe User
getUserEmail :: User -> Maybe String

sendWelcomeEmail :: String -> Maybe ()
sendWelcomeEmailToUser :: String -> Maybe ()
sendWelcomeEmailToUser username = do
    user <- findUser username
    email <- getUserEmail user
    sendWelcomeEmail email

在这个例子中:

通过 do 语法,可以一步一步提取 Maybe 值,并处理所有可能的失败情况。如果在任意一步返回了 Nothing,整个操作将自动返回 Nothing

如果没有 do 语法,这段代码可能会变成嵌套的 >>= 操作符,非常难以阅读:


sendWelcomeEmailToUser username =
    findUser username >>= \user ->
    getUserEmail user >>= \email ->
    sendWelcomeEmail email

3. 列表 Monad 中的 do

列表也是一个 Monad,do 语法同样可以用于列表操作。列表 Monad 的语义是“非确定性计算”,即从多个可能的值中进行选择。

例如,生成笛卡尔积:


pairs :: [a] -> [b] -> [(a, b)]
pairs xs ys = do
    x <- xs
    y <- ys
    return (x, y)

在这里,do 语法用来简化从列表中选择元素的过程,最终生成了所有可能的 (x, y) 组合。

等价的代码使用列表 Monad 的 >>= 操作符:

pairs xs ys = xs >>= \x ->
              ys >>= \y ->
              return (x, y)

do 语法的工作原理

do 语法只是 >>=return 的语法糖,本质上并没有改变 Haskell 中 Monad 的工作机制。对于每一个 <- 绑定,Haskell 会在背后用 >>= 来解包 Monad。

例如:


main = do
    a <- action1
    b <- action2
    action3

等价于:

main =
    action1 >>= \a ->
    action2 >>= \b ->
    action3

其中 action1action2 都是 Monad 操作,它们的结果通过 >>= 绑定到 ab,而 action3 不需要绑定结果。

do 语法总结

通过 do 语法,Haskell 保留了 Monad 操作的抽象能力,同时为程序员提供了更具可读性的代码表达方式。、

askForName :: IO ()
askForName = putStrLn "What is your name?"
nameStatement :: String -> String
nameStatement name = "Hello, " ++ name ++ "!"

helloName :: IO ()
helloName = askForName >>
getLine >>=
(\name ->
return (nameStatement name)) >>=
putStrL

askForName :: IO ()
askForName = putStrLn "What is your name?"
nameStatement :: String -> String
nameStatement name = "Hello, " ++ name ++ "!"

helloName :: IO ()
helloName = do
    askForName             -- 输出询问语句
    name <- getLine        -- 从输入获取用户名字
    putStrLn (nameStatement name)  -- 输出个性化问候

Suggest Changes

Previous Post
monad
Next Post
functor