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语法要点:
- 基本结构:
do { action1; action2 } - 绑定:
x <- action提取值 - let绑定:
let x = value定义纯值 - 缩进:保持一致的对齐
- 嵌套:可以在条件、case中使用do
- return:在do块中返回值
- 最佳实践:保持简洁、避免深层嵌套
掌握do语法后,让我们继续学习文件操作。
最后更新:2026-03-27