Guile/Scheme 编程教程 / 第5章:函数与闭包
第 5 章:函数与闭包
5.1 函数定义
在 Scheme 中,函数是一等公民(first-class citizen),可以像普通值一样传递和操作。
5.1.1 基本定义方式
;; 方式一:define 的简写形式(最常用)
(define (square x)
(* x x))
;; 方式二:define 的完整形式(lambda 表达式)
(define square
(lambda (x)
(* x x))
;; 两者完全等价!
;; 多参数函数
(define (add a b)
(+ a b))
;; 无参数函数
(define (greet)
(display "Hello!")
(newline))
;; 可变参数函数(rest 参数)
(define (sum first . rest)
(apply + first rest))
(sum 1 2 3 4 5) ; => 15
;; 更常见的可变参数写法
(define (sum-all . args)
(apply + args))
(sum-all 1 2 3) ; => 6
;; 关键字参数(Guile 扩展)
(define* (make-user name #:key (age 0) (email ""))
`((name . ,name) (age . ,age) (email . ,email)))
(make-user "Alice" #:age 30 #:email "[email protected]")
;; => ((name . "Alice") (age . 30) (email . "[email protected]"))
5.1.2 函数的多种形式
;; 单表达式函数
(define (double x) (* x 2))
;; 多表达式函数(隐式 begin)
(define (print-and-return x)
(display "值为: ")
(display x)
(newline)
x)
;; 带文档的函数(通过注释)
;;; calculate-area — 计算圆的面积
;;; 参数 r: 半径
;;; 返回: 面积值
(define (calculate-area r)
(* 3.14159 r r))
;; 使用 case-lambda(不同参数数量有不同行为)
(use-modules (ice-9 curried-definitions))
;; Guile 的 case-lambda
(define greet
(case-lambda
(() "Hello, anonymous!")
((name) (string-append "Hello, " name "!"))
((name time) (string-append "Good " time ", " name "!"))))
(greet) ; => "Hello, anonymous!"
(greet "Guile") ; => "Hello, Guile!"
(greet "Guile" "morning") ; => "Good morning, Guile!"
5.2 Lambda 表达式
Lambda 是创建匿名函数的表达式,是 Scheme 的核心概念。
5.2.1 Lambda 基础
;; Lambda 表达式的基本语法
;; (lambda (参数列表) 函数体)
;; 创建并立即调用
((lambda (x) (* x x)) 5) ; => 25
;; 绑定到变量
(define cube (lambda (x) (* x x x)))
(cube 3) ; => 27
;; 作为参数传递
(map (lambda (x) (* x 2)) '(1 2 3 4 5))
;; => (2 4 6 8 10)
;; 多参数 lambda
(define dist
(lambda (x1 y1 x2 y2)
(sqrt (+ (expt (- x2 x1) 2)
(expt (- y2 y1) 2)))))
(dist 0 0 3 4) ; => 5.0
;; 无参数 lambda
(define get-time
(lambda () (current-time)))
5.2.2 Lambda 与作用域
;; Lambda 捕获定义时的环境(词法作用域)
(define x 10)
(define f (lambda () x)) ; f 捕获了 x=10
(set! x 20)
(f) ; => 20(注意:捕获的是变量的引用,不是值)
;; 更安全的做法:let 绑定
(define make-counter
(lambda (initial)
(let ((count initial))
(lambda ()
(set! count (+ count 1))
count))))
(define counter (make-counter 0))
(counter) ; => 1
(counter) ; => 2
(counter) ; => 3
5.3 闭包(Closure)
闭包是函数与其捕获的环境的组合。这是 Scheme 最强大的特性之一。
5.3.1 闭包的本质
┌─ 闭包的结构 ─────────────────────────────────┐
│ │
│ ┌──────────────────────────────┐ │
│ │ 闭包 │ │
│ │ ┌────────────────────────┐ │ │
│ │ │ 函数体 │ │ │
│ │ │ (lambda (x) (+ x y)) │ │ │
│ │ └────────────────────────┘ │ │
│ │ ┌────────────────────────┐ │ │
│ │ │ 捕获的环境 │ │ │
│ │ │ y → 42 │ │ │
│ │ └────────────────────────┘ │ │
│ └──────────────────────────────┘ │
│ │
└───────────────────────────────────────────────┘
;; 闭包示例 1:捕获外部变量
(define (make-adder n)
(lambda (x) (+ x n))) ; 闭包捕获了 n
(define add5 (make-adder 5))
(define add10 (make-adder 10))
(add5 3) ; => 8
(add10 3) ; => 13
;; 闭包示例 2:私有状态
(define (make-bank-account balance)
(let ((bal balance))
(lambda (msg)
(cond
((eq? msg 'deposit)
(lambda (amount)
(set! bal (+ bal amount))
bal))
((eq? msg 'withdraw)
(lambda (amount)
(if (>= bal amount)
(begin (set! bal (- bal amount)) bal)
(error "余额不足"))))
((eq? msg 'balance)
(lambda () bal))
(else (error "未知操作"))))))
(define account (make-bank-account 1000))
((account 'balance)) ; => 1000
((account 'deposit) 500) ; => 1500
((account 'withdraw) 200) ; => 1300
((account 'balance)) ; => 1300
5.3.2 闭包的高级应用
;; 1. 函数工厂
(define (make-validator predicate error-msg)
(lambda (value)
(if (predicate value)
value
(error error-msg value))))
(define validate-positive
(make-validator positive? "必须是正数"))
(define validate-string
(make-validator string? "必须是字符串"))
(validate-positive 5) ; => 5
;; (validate-positive -1) ; => ERROR: 必须是正数 -1
;; 2. 缓存(Memoization)
(define (memoize f)
(let ((cache (make-hash-table)))
(lambda args
(or (hash-ref cache args)
(let ((result (apply f args)))
(hash-set! cache args result)
result)))))
(define slow-fib
(memoize
(lambda (n)
(if (<= n 1)
n
(+ (slow-fib (- n 1))
(slow-fib (- n 2)))))))
(slow-fib 100) ; => 354224848179261915075(瞬间完成)
;; 3. 事件系统
(define (make-event-emitter)
(let ((handlers (make-hash-table)))
(lambda (action . args)
(cond
((eq? action 'on)
(let ((event (car args))
(handler (cadr args)))
(hash-set! handlers event
(cons handler
(or (hash-ref handlers event) '())))))
((eq? action 'emit)
(let ((event (car args))
(data (cdr args)))
(for-each
(lambda (h) (apply h data))
(or (hash-ref handlers event) '()))))))))
(define emitter (make-event-emitter))
(emitter 'on 'click (lambda (x y) (format #t "点击 (~a, ~a)~%" x y)))
(emitter 'emit 'click 100 200)
;; 输出: 点击 (100, 200)
5.4 高阶函数
高阶函数是接受函数作为参数或返回函数的函数。
5.4.1 内置高阶函数
;; map —— 将函数应用于列表的每个元素
(map + '(1 2 3) '(10 20 30)) ; => (11 22 33)
(map (lambda (x y) (+ x y)) '(1 2) '(10 20)) ; => (11 22)
;; for-each —— 类似 map 但不返回结果(副作用用)
(for-each display '(1 2 3 4 5))
;; 输出: 12345
;; apply —— 将函数应用于参数列表
(apply + '(1 2 3 4 5)) ; => 15
(apply max '(3 1 4 1 5 9)) ; => 9
;; fold(左折叠)
(fold + 0 '(1 2 3 4 5)) ; => 15
;; 等价于 (+ 5 (+ 4 (+ 3 (+ 2 (+ 1 0)))))
;; fold-right(右折叠)
(fold-right cons '() '(1 2 3)) ; => (1 2 3)
;; 等价于 (cons 1 (cons 2 (cons 3 '())))
;; filter —— 过滤
(filter even? '(1 2 3 4 5 6)) ; => (2 4 6)
;; any / every
(any even? '(1 3 5 6 7)) ; => #t
(every positive? '(1 2 3)) ; => #t
5.4.2 自定义高阶函数
;; 1. compose —— 函数组合
(define (compose f g)
(lambda args
(f (apply g args))))
(define square-then-double (compose (lambda (x) (* x 2))
(lambda (x) (* x x))))
(square-then-double 3) ; => 18 ((3²)×2)
;; 2. curry —— 柯里化
(define (curry f . args)
(lambda rest
(apply f (append args rest))))
(define add (curry +))
(define add5 (add 5))
(add5 3) ; => 8
;; 3. negate —— 取反谓词
(define (negate pred)
(lambda args (not (apply pred args))))
(define odd-list? (negate even?))
(filter odd-list? '(1 2 3 4 5)) ; => (1 3 5)
;; 4. partial —— 部分应用
(define (partial f . args)
(lambda rest
(apply f (append args rest))))
(define log-info (partial format #t "[INFO] ~a~%"))
(log-info "系统启动")
;; 输出: [INFO] 系统启动
;; 5. reduce —— 简化的 fold
(define (reduce f lst)
(if (null? (cdr lst))
(car lst)
(f (car lst) (reduce f (cdr lst)))))
(reduce + '(1 2 3 4 5)) ; => 15
5.4.3 管道操作(Pipe)
;; 实现管道操作符(类似 Unix 管道)
(define (pipe . fns)
(lambda (x)
(fold (lambda (f acc) (f acc)) x fns)))
(define process
(pipe
(lambda (s) (string-trim-both s))
string-downcase
(lambda (s) (string-split s #\space))
(lambda (lst) (string-join lst "-"))))
(process " Hello World FOO ")
;; => "hello-world-foo"
;; SRFI-26 的 cut/cute(部分应用的语法糖)
(use-modules (srfi srfi-26))
(map (cut * 2 <>) '(1 2 3 4)) ; => (2 4 6 8)
(map (cut + 1 <>) '(1 2 3)) ; => (2 3 4)
(filter (cut > <> 3) '(1 2 3 4 5)) ; => (4 5)
5.5 命名 let(Named Let)
命名 let 是一种优雅的循环/递归模式。
5.5.1 基本语法
;; 命名 let 的语法:
;; (let loop-name ((var1 init1) (var2 init2) ...)
;; body)
;; 等价于:
;; ((letrec ((loop-name (lambda (var1 var2 ...) body)))
;; loop-name)
;; init1 init2 ...)
;; 示例:求和
(let loop ((i 1) (acc 0))
(if (> i 10)
acc
(loop (+ i 1) (+ acc i))))
; => 55
5.5.2 命名 let 的典型用法
;; 1. 遍历列表
(let loop ((lst '(1 2 3 4 5))
(result '()))
(if (null? lst)
(reverse result)
(loop (cdr lst)
(cons (* (car lst) (car lst)) result))))
; => (1 4 9 16 25)
;; 2. 字符串处理
(let loop ((chars (string->list "Hello, World!"))
(count 0))
(cond
((null? chars) count)
((char-whitespace? (car chars))
(loop (cdr chars) (+ count 1)))
(else (loop (cdr chars) count))))
; => 2
;; 3. 查找元素
(define (find-first pred lst)
(let loop ((rest lst))
(cond
((null? rest) #f)
((pred (car rest)) (car rest))
(else (loop (cdr rest))))))
(find-first even? '(1 3 4 7 8)) ; => 4
;; 4. 数值迭代(牛顿法求平方根)
(define (sqrt-newton x)
(let loop ((guess 1.0))
(if (< (abs (- (* guess guess) x)) 1e-10)
guess
(loop (/ (+ guess (/ x guess)) 2)))))
(sqrt-newton 2) ; => 1.4142135623730951
5.6 递归与迭代
5.6.1 递归模式总结
| 模式 | 特点 | 示例 |
|---|---|---|
| 线性递归 | O(n) 空间 | 阶乘(非尾递归版) |
| 尾递归 | O(1) 空间 | 阶乘(尾递归版) |
| 树递归 | 指数复杂度 | 斐波那契 |
| 相互递归 | 两个函数互相调用 | 奇偶判断 |
| 命名 let | 语法糖 | 循环替代 |
;; 相互递归示例
(define (my-odd? n)
(if (= n 0) #f (my-even? (- n 1))))
(define (my-even? n)
(if (= n 0) #t (my-odd? (- n 1))))
(my-odd? 5) ; => #t
(my-even? 4) ; => #t
5.6.2 递归转迭代
;; 原始递归版本
(define (product lst)
(if (null? lst)
1
(* (car lst) (product (cdr lst)))))
;; 转换为尾递归(使用累加器)
(define (product-tail lst)
(let loop ((rest lst) (acc 1))
(if (null? rest)
acc
(loop (cdr rest) (* (car rest) acc)))))
;; 使用 fold 更简洁
(define (product-fold lst)
(fold * 1 lst))
;; 三种方式结果相同
(product '(1 2 3 4 5)) ; => 120
(product-tail '(1 2 3 4 5)) ; => 120
(product-fold '(1 2 3 4 5)) ; => 120
5.7 Continuation(续延)
续延是 Scheme 最独特的特性之一,它代表"程序剩余的计算"。
;; call/cc 基础
(+ 1 (call/cc
(lambda (k)
(+ 2 (k 3)))))
;; 结果: 4
;; 解释:
;; 1. (call/cc ...) 捕获续延 k
;; 2. k 代表 "(+ 1 ?)" 这个上下文
;; 3. (k 3) 将 3 传入续延,等价于 (+ 1 3)
;; 4. 外面的 (+ 2 ...) 被跳过
;; 用续延实现提前返回
(define (find-even lst)
(call/cc
(lambda (return)
(for-each
(lambda (x)
(when (even? x)
(return x)))
lst)
#f))) ; 没找到返回 #f
(find-even '(1 3 5 6 7)) ; => 6
;; 用续延实现异常处理(概念性示例)
(define (with-handler handler thunk)
(call/cc
(lambda (k)
(parameterize ((error-escape k))
(thunk)))))
注意:续延是高级特性,初学者不必深入理解。了解概念即可,在实际项目中使用时要谨慎。
5.8 柯里化与部分应用
;; 手动柯里化
(define (curried-add a)
(lambda (b)
(+ a b)))
((curried-add 3) 4) ; => 7
;; 使用 SRFI-26 的 cut
(use-modules (srfi srfi-26))
(define add5 (cut + 5 <>))
(add5 3) ; => 8
;; 更多 cut 示例
(define multiply-by-3 (cut * 3 <>))
(multiply-by-3 7) ; => 21
(define greet (cut string-append "Hello, " <> "!"))
(greet "Guile") ; => "Hello, Guile!"
;; 使用 cute(严格求值版,立即求值捕获的参数)
(define x 10)
(define add-x (cut + <> x)) ; cut 使用延迟求值 x
(define add-x2 (cute + <> x)) ; cute 立即求值 x 为 10
5.9 业务场景
5.9.1 中间件模式(Web 框架)
;; 中间件模式:函数层层包装
(define (logging-middleware handler)
(lambda (request)
(format #t "[LOG] ~a ~a~%"
(assq-ref request 'method)
(assq-ref request 'path))
(let ((response (handler request)))
(format #t "[LOG] 响应: ~a~%" (assq-ref response 'status))
response)))
(define (auth-middleware handler)
(lambda (request)
(if (assq-ref request 'token)
(handler request)
'((status . 403) (body . "Unauthorized")))))
(define (handle-request request)
'((status . 200) (body . "Hello, World!")))
;; 组合中间件
(define app
(logging-middleware
(auth-middleware
handle-request)))
(app '((method . GET) (path . "/") (token . "abc")))
;; [LOG] GET /
;; [LOG] 响应: 200
;; => ((status . 200) (body . "Hello, World!"))
5.9.2 策略模式
;; 策略模式:通过函数参数切换算法
(define (sort-with-strategy lst strategy)
(sort lst strategy))
(define data '((3 . "c") (1 . "a") (2 . "b")))
;; 按键排序
(sort-with-strategy data (lambda (a b) (< (car a) (car b))))
;; => ((1 . "a") (2 . "b") (3 . "c"))
;; 按值排序
(sort-with-strategy data (lambda (a b) (string<? (cdr a) (cdr b))))
;; => ((1 . "a") (2 . "b") (3 . "c"))
5.10 本章小结
| 主题 | 要点 |
|---|---|
| 函数定义 | define 和 lambda 两种形式 |
| 闭包 | 函数 + 捕获的环境 |
| 高阶函数 | map/filter/fold/apply |
| 命名 let | 优雅的循环模式 |
| 续延 | 程序剩余计算的抽象 |
| 部分应用 | cut/cute、curry |
扩展阅读
上一章:第 4 章:列表与序对 下一章:第 6 章:控制流