7Seven Languages in Seven Weeks

Haskell

纯函数式

纯函数类型类Monad惰性求值

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第7章:Haskell(纯函数式)

一、导读

1.1 本章学习目标

第7章介绍 Haskell——一门纯函数式语言。Haskell 将函数式编程推向极致:没有可变状态、没有副作用、所有函数都是纯函数。

本章的核心学习目标包括:

  • 理解纯函数式编程:没有副作用、引用透明
  • 掌握类型类(Type Class):Haskell 的多态机制
  • 理解 Monad:如何在纯函数式中处理副作用
  • 体验惰性求值:表达式只在需要时才计算

1.2 为什么本章如此重要

Haskell 是函数式编程的"教科书"。它的设计影响了 Swift、Kotlin、Rust 等众多现代语言。理解 Haskell 的类型系统、Monad、惰性求值,能让你更深刻地理解函数式编程的本质。很多编程语言的高级特性——泛型、模式匹配、代数数据类型——都能在 Haskell 中找到最纯粹的形态。

1.3 核心问题引导

纯函数式编程没有副作用,那它如何与现实世界交互?

什么是 Monad?为什么它被称为"函数式编程的终极抽象"?

类型类和 Java 的接口有什么异同?

惰性求值有什么优势和劣势?


二、核心概念详解

2.1 纯函数

haskell
-- 纯函数:相同输入总是产生相同输出,没有副作用
add :: Int -> Int -> Int
add x y = x + y

-- 高阶函数
applyTwice :: (a -> a) -> a -> a
applyTwice f x = f (f x)

-- 模式匹配
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)

-- 守卫
abs' :: Int -> Int
abs' n
  | n < 0     = -n
  | otherwise = n

2.2 类型类

haskell
-- 类型类定义接口
class Eq a where
  (==) :: a -> a -> Bool
  (/=) :: a -> a -> Bool
  x /= y = not (x == y)

-- 实例
instance Eq Int where
  x == y = primEqInt x y

-- 带约束的函数
elem :: (Eq a) => a -> [a] -> Bool
elem _ [] = False
elem x (y:ys) = x == y || elem x ys

-- 常用类型类
-- Num: 数字操作
-- Ord: 有序比较
-- Show: 转为字符串
-- Read: 从字符串解析
-- Functor: 可映射
-- Monad: 可绑定

2.3 代数数据类型

haskell
-- 代数数据类型
data Shape = Circle Double | Rectangle Double Double
  deriving (Show)

area :: Shape -> Double
area (Circle r) = pi * r * r
area (Rectangle w h) = w * h

-- Maybe 类型
data Maybe a = Nothing | Just a
  deriving (Show)

safeDiv :: Double -> Double -> Maybe Double
safeDiv _ 0 = Nothing
safeDiv x y = Just (x / y)

-- Either 类型
data Either a b = Left a | Right b

2.4 Monad

haskell
-- Monad 类型类
class Monad m where
  return :: a -> m a
  (>>=) :: m a -> (a -> m b) -> m b

-- Maybe 是 Monad
instance Maybe Monad where
  return = Just
  Nothing >>= _ = Nothing
  (Just x) >>= f = f x

-- 使用 do 语法
lookupUser :: String -> Maybe User
lookupUser name = do
  userId <- findUserId name
  user <- getUserById userId
  return user

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

2.5 惰性求值

haskell
-- 惰性求值:表达式只在需要时才计算
-- 无限列表
naturals = [1..]
evens = filter even naturals
first10Evens = take 10 evens    -- [2,4,6,8,10,12,14,16,18,20]

-- 斐波那契数列
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
first20Fibs = take 20 fibs

-- 惰性求值的好处
-- 1. 可以处理无限数据结构
-- 2. 只计算需要的部分
-- 3. 可以组合操作而不创建中间结果

三、实战案例

3.1 案例一:用 Monad Transformer 构建 Web 应用

haskell
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

import Control.Monad.Reader
import Control.Monad.Except
import Control.Monad.State

-- 应用类型
newtype App a = App
  { runApp :: ReaderT AppConfig (ExceptT AppError (StateT AppState IO)) a
  } deriving (Functor, Applicative, Monad, MonadIO,
              MonadReader AppConfig, MonadError AppError, MonadState AppState)

data AppConfig = AppConfig { dbUrl :: String, port :: Int }
data AppState = AppState { requestCount :: Int }
data AppError = NotFound | Unauthorized deriving (Show)

-- 使用
handleRequest :: String -> App String
handleRequest path = do
  config <- ask
  state <- get
  put state { requestCount = requestCount state + 1 }
  
  case path of
    "/health" -> return "OK"
    _ -> throwError NotFound

四、本章小结

纯函数式:没有副作用,引用透明,易于推理。

类型类:Haskell 的多态机制,比 Java 接口更灵活。

Monad:在纯函数式中处理副作用的抽象。

惰性求值:表达式只在需要时计算,支持无限数据结构。


五、思考与练习

纯函数式编程的"没有副作用"如何与现实世界的 I/O 交互?Monad 是如何解决这个问题的?

Haskell 的类型类和 Java 的接口有什么本质区别?

惰性求值的优势是什么?它有什么潜在的性能问题?


7/7Hask
0%