IO基础 #

一、IO概念 #

1.1 什么是IO #

IO(Input/Output)是Haskell处理副作用的机制:

  • 读取输入
  • 写入输出
  • 文件操作
  • 网络通信
  • 随机数
  • 状态修改

1.2 IO类型 #

haskell
-- IO是一个类型构造器
-- IO a 表示一个产生类型a值的IO操作

-- 示例
getLine :: IO String          -- 读取一行
putStrLn :: String -> IO ()   -- 输出一行
getChar :: IO Char            -- 读取一个字符

1.3 纯函数与IO #

haskell
-- 纯函数:相同输入总是产生相同输出
add :: Int -> Int -> Int
add x y = x + y

-- IO操作:有副作用
main :: IO ()
main = do
    putStrLn "Enter your name:"
    name <- getLine
    putStrLn ("Hello, " ++ name ++ "!")

二、基本IO操作 #

2.1 输出 #

haskell
-- putStrLn:输出字符串并换行
putStrLn :: String -> IO ()

-- putStr:输出字符串不换行
putStr :: String -> IO ()

-- putChar:输出单个字符
putChar :: Char -> IO ()

-- print:输出任意Show类型
print :: Show a => a -> IO ()

-- 示例
main :: IO ()
main = do
    putStrLn "Hello, World!"
    putStr "No newline"
    putChar '!'
    print 42

2.2 输入 #

haskell
-- getLine:读取一行
getLine :: IO String

-- getChar:读取一个字符
getChar :: IO Char

-- getContents:读取全部内容(惰性)
getContents :: IO String

-- 示例
main :: IO ()
main = do
    putStrLn "Enter your name:"
    name <- getLine
    putStrLn ("Hello, " ++ name ++ "!")

2.3 交互 #

haskell
-- interact:将函数应用于整个输入
interact :: (String -> String) -> IO ()

-- 示例:将输入转为大写
main :: IO ()
main = interact (map toUpper)

-- 示例:只输出包含特定字符串的行
main :: IO ()
main = interact (unlines . filter (elem 'a') . lines)

三、IO组合 #

3.1 顺序执行 #

haskell
-- 使用>>顺序执行
(>>) :: IO a -> IO b -> IO b

-- 示例
main :: IO ()
main = 
    putStrLn "First" >>
    putStrLn "Second" >>
    putStrLn "Third"

3.2 绑定操作 #

haskell
-- 使用>>=绑定IO结果
(>>=) :: IO a -> (a -> IO b) -> IO b

-- 示例
main :: IO ()
main = 
    getLine >>= \name ->
    putStrLn ("Hello, " ++ name)

3.3 return #

haskell
-- return:将值包装为IO
return :: a -> IO a

-- 示例
main :: IO ()
main = do
    x <- return 5
    print x  -- 5

-- 注意:return不是返回语句
-- 它只是将值包装为IO

四、IO与纯函数 #

4.1 隔离副作用 #

haskell
-- 好:纯函数与IO分离
processName :: String -> String
processName name = "Hello, " ++ name ++ "!"

main :: IO ()
main = do
    name <- getLine
    putStrLn (processName name)

-- 不好:在纯函数中做IO
-- processName name = unsafePerformIO (putStrLn "debug")

4.2 从IO提取值 #

haskell
-- 不能直接从IO提取值
-- 错误示例:
-- name :: String
-- name = getLine  -- 类型错误!

-- 正确方式:在IO上下文中使用
main :: IO ()
main = do
    name <- getLine  -- name :: String
    putStrLn name

4.3 fmap和IO #

haskell
-- IO是Functor
-- 可以使用fmap处理IO结果

-- 示例
main :: IO ()
main = do
    -- fmap处理IO结果
    upperName <- fmap (map toUpper) getLine
    putStrLn upperName

    -- 等价于
    name <- getLine
    let upperName' = map toUpper name
    putStrLn upperName'

五、IO工具函数 #

5.1 sequence #

haskell
import Control.Monad

-- sequence:将IO列表转为IO的列表
sequence :: [IO a] -> IO [a]

-- 示例
main :: IO ()
main = do
    results <- sequence [getLine, getLine, getLine]
    print results

-- sequence_:丢弃结果
sequence_ :: [IO a] -> IO ()

main' :: IO ()
main' = sequence_ [putStrLn "One", putStrLn "Two", putStrLn "Three"]

5.2 mapM #

haskell
import Control.Monad

-- mapM:映射后序列
mapM :: (a -> IO b) -> [a] -> IO [b]

-- 示例
main :: IO ()
main = do
    results <- mapM getLine ["First: ", "Second: ", "Third: "]
    print results

-- mapM_:丢弃结果
mapM_ :: (a -> IO b) -> [a] -> IO ()

main' :: IO ()
main' = mapM_ putStrLn ["One", "Two", "Three"]

5.3 forM #

haskell
import Control.Monad

-- forM:mapM的参数翻转
forM :: [a] -> (a -> IO b) -> IO [b]

-- 示例
main :: IO ()
main = do
    results <- forM [1..3] $ \i -> do
        putStrLn $ "Enter name " ++ show i ++ ":"
        getLine
    print results

5.4 when和unless #

haskell
import Control.Monad

-- when:条件为True时执行
when :: Bool -> IO () -> IO ()

-- unless:条件为False时执行
unless :: Bool -> IO () -> IO ()

-- 示例
main :: IO ()
main = do
    putStrLn "Enter a number:"
    n <- readLn
    when (n > 0) $ putStrLn "Positive!"
    when (n < 0) $ putStrLn "Negative!"
    when (n == 0) $ putStrLn "Zero!"

六、实践示例 #

6.1 简单计算器 #

haskell
main :: IO ()
main = do
    putStrLn "Simple Calculator"
    putStrLn "Enter first number:"
    a <- readLn :: IO Int
    putStrLn "Enter second number:"
    b <- readLn :: IO Int
    putStrLn "Enter operation (+, -, *, /):"
    op <- getLine
    case op of
        "+" -> print (a + b)
        "-" -> print (a - b)
        "*" -> print (a * b)
        "/" -> print (a `div` b)
        _   -> putStrLn "Unknown operation"

6.2 猜数字游戏 #

haskell
import System.Random

main :: IO ()
main = do
    target <- randomRIO (1, 100) :: IO Int
    putStrLn "I'm thinking of a number between 1 and 100"
    gameLoop target

gameLoop :: Int -> IO ()
gameLoop target = do
    putStrLn "Enter your guess:"
    guess <- readLn :: IO Int
    if guess < target
        then do
            putStrLn "Too low!"
            gameLoop target
        else if guess > target
            then do
                putStrLn "Too high!"
                gameLoop target
            else putStrLn "Correct! You win!"

6.3 文件复制 #

haskell
main :: IO ()
main = do
    putStrLn "Enter source file:"
    src <- getLine
    putStrLn "Enter destination file:"
    dst <- getLine
    contents <- readFile src
    writeFile dst contents
    putStrLn "File copied successfully!"

七、总结 #

IO基础要点:

  1. IO类型IO a 表示产生类型a值的IO操作
  2. 基本操作putStrLngetLineprint
  3. 组合操作>>>>=do语法
  4. return:将值包装为IO
  5. 工具函数sequencemapMforMwhen
  6. 隔离副作用:纯函数与IO分离

掌握IO基础后,让我们继续学习do语法。

最后更新:2026-03-27