强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 章:控制流