do语法 #

一、do语法基础 #

1.1 什么是do语法 #

do语法是处理Monad操作(如IO)的语法糖:

haskell
-- do语法
main :: IO ()
main = do
    putStrLn "Hello"
    putStrLn "World"

-- 等价于
main' :: IO ()
main' = putStrLn "Hello" >> putStrLn "World"

1.2 基本结构 #

haskell
-- do块结构
do
    action1
    action2
    action3

-- 带绑定
do
    x <- action1
    y <- action2
    action3 x y

1.3 简单示例 #

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

二、绑定操作 #

2.1 <-绑定 #

haskell
-- <- 从IO操作中提取值
main :: IO ()
main = do
    line <- getLine        -- line :: String
    char <- getChar        -- char :: Char
    num <- readLn :: IO Int  -- num :: Int
    print (line, char, num)

2.2 let绑定 #

haskell
-- let在do块中定义纯值
main :: IO ()
main = do
    x <- readLn :: IO Int
    let doubled = x * 2
    let squared = x * x
    print (doubled, squared)

2.3 <-与let的区别 #

haskell
main :: IO ()
main = do
    -- <- 用于IO操作
    input <- getLine        -- 从IO提取值
    
    -- let 用于纯计算
    let processed = map toUpper input
    
    putStrLn processed

三、do语法规则 #

3.1 缩进规则 #

haskell
-- 正确:对齐缩进
main = do
    action1
    action2
    action3

-- 错误:缩进不一致
-- main = do
--     action1
--   action2  -- 错误!

3.2 单行do #

haskell
-- 单行形式(使用分号)
main = do { putStrLn "Hello"; putStrLn "World" }

-- 带绑定
main' = do { x <- getLine; putStrLn x }

3.3 最后一个表达式 #

haskell
-- do块的最后一个表达式决定返回类型
getName :: IO String
getName = do
    putStrLn "Enter your name:"
    getLine  -- 返回IO String

-- 如果不需要返回值
main :: IO ()
main = do
    name <- getName
    putStrLn ("Hello, " ++ name)

四、嵌套do #

4.1 嵌套结构 #

haskell
main :: IO ()
main = do
    putStrLn "Outer do"
    do
        putStrLn "Inner do"
        putStrLn "Still inner"
    putStrLn "Back to outer"

4.2 条件中的do #

haskell
main :: IO ()
main = do
    putStrLn "Enter a number:"
    n <- readLn :: IO Int
    if n > 0
        then do
            putStrLn "Positive"
            putStrLn "Great!"
        else do
            putStrLn "Non-positive"
            putStrLn "Try again"

4.3 case中的do #

haskell
main :: IO ()
main = do
    putStrLn "Enter a command:"
    cmd <- getLine
    case cmd of
        "hello" -> do
            putStrLn "Hello to you too!"
        "bye" -> do
            putStrLn "Goodbye!"
        _ -> do
            putStrLn "Unknown command"

五、do与Monad #

5.1 do是语法糖 #

haskell
-- do语法
main = do
    x <- action1
    y <- action2
    action3 x y

-- 等价于
main' = action1 >>= \x ->
        action2 >>= \y ->
        action3 x y

5.2 >>运算符 #

haskell
-- do语法
main = do
    action1
    action2

-- 等价于
main' = action1 >> action2

5.3 return在do中 #

haskell
-- return用于返回值
getInput :: IO String
getInput = do
    line <- getLine
    return (map toUpper line)

-- 使用
main :: IO ()
main = do
    upper <- getInput
    putStrLn upper

六、常见模式 #

6.1 链式操作 #

haskell
main :: IO ()
main = do
    putStrLn "Step 1: Enter name"
    name <- getLine
    
    putStrLn "Step 2: Enter age"
    age <- readLn :: IO Int
    
    putStrLn "Step 3: Enter email"
    email <- getLine
    
    putStrLn $ "User: " ++ name ++ ", " ++ show age ++ ", " ++ email

6.2 循环模式 #

haskell
main :: IO ()
main = do
    putStrLn "Enter commands (quit to exit):"
    loop

loop :: IO ()
loop = do
    cmd <- getLine
    if cmd == "quit"
        then putStrLn "Goodbye!"
        else do
            putStrLn $ "You entered: " ++ cmd
            loop

6.3 累加器模式 #

haskell
main :: IO ()
main = do
    putStrLn "Enter numbers (0 to finish):"
    sum <- readNumbers 0
    putStrLn $ "Sum: " ++ show sum

readNumbers :: Int -> IO Int
readNumbers total = do
    n <- readLn :: IO Int
    if n == 0
        then return total
        else readNumbers (total + n)

6.4 收集结果 #

haskell
main :: IO ()
main = do
    putStrLn "Enter 3 names:"
    names <- collectNames 3
    print names

collectNames :: Int -> IO [String]
collectNames 0 = return []
collectNames n = do
    name <- getLine
    rest <- collectNames (n - 1)
    return (name : rest)

七、实践示例 #

7.1 交互式菜单 #

haskell
main :: IO ()
main = do
    putStrLn "=== Menu ==="
    putStrLn "1. Say Hello"
    putStrLn "2. Say Goodbye"
    putStrLn "3. Exit"
    menuLoop

menuLoop :: IO ()
menuLoop = do
    putStrLn "Enter choice:"
    choice <- getLine
    case choice of
        "1" -> do
            putStrLn "Hello!"
            menuLoop
        "2" -> do
            putStrLn "Goodbye!"
            menuLoop
        "3" -> putStrLn "Exiting..."
        _   -> do
            putStrLn "Invalid choice"
            menuLoop

7.2 文件处理 #

haskell
main :: IO ()
main = do
    putStrLn "Enter filename:"
    filename <- getLine
    contents <- readFile filename
    let lines' = lines contents
        count = length lines'
    putStrLn $ "File has " ++ show count ++ " lines"
    putStrLn "First 5 lines:"
    mapM_ putStrLn (take 5 lines')

7.3 数据收集 #

haskell
data Person = Person
    { personName :: String
    , personAge  :: Int
    } deriving (Show)

main :: IO ()
main = do
    people <- collectPeople
    putStrLn "Collected people:"
    mapM_ print people

collectPeople :: IO [Person]
collectPeople = do
    putStrLn "Enter name (empty to finish):"
    name <- getLine
    if null name
        then return []
        else do
            putStrLn "Enter age:"
            age <- readLn :: IO Int
            rest <- collectPeople
            return (Person name age : rest)

八、最佳实践 #

8.1 保持do块简洁 #

haskell
-- 好:提取函数
main :: IO ()
main = do
    input <- getInput
    let result = process input
    outputResult result

-- 不好:do块太长
-- main = do
--     ... 50行代码 ...

8.2 避免深层嵌套 #

haskell
-- 好:提前返回
main :: IO ()
main = do
    input <- getLine
    if null input
        then putStrLn "Empty input"
        else do
            let result = process input
            putStrLn result

-- 不好:深层嵌套
-- main = do
--     input <- getLine
--     if not (null input)
--         then do
--             if valid input
--                 then do
--                     ...

8.3 使用where和let #

haskell
main :: IO ()
main = do
    input <- getLine
    let processed = process input
    putStrLn processed
  where
    process = map toUpper . filter isLetter

九、总结 #

do语法要点:

  1. 基本结构do { action1; action2 }
  2. 绑定x <- action 提取值
  3. let绑定let x = value 定义纯值
  4. 缩进:保持一致的对齐
  5. 嵌套:可以在条件、case中使用do
  6. return:在do块中返回值
  7. 最佳实践:保持简洁、避免深层嵌套

掌握do语法后,让我们继续学习文件操作。

最后更新:2026-03-27