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

Guile/Scheme 编程教程 / 第9章:模块系统

第 9 章:模块系统

9.1 模块基础

模块系统是管理大型 Guile 项目的关键机制,提供了命名空间隔离、接口封装和代码复用的能力。

9.1.1 为什么需要模块

没有模块的世界:                    有模块的世界:
┌──────────────────┐              ┌──────────────────┐
│ (define x 1)     │              │ (define-module    │
│ (define y 2)     │              │   (my-module)     │
│ (define x 10)    │ ← 冲突!     │   #:export (x))   │
│ (define list 42) │ ← 覆盖!     │ (define x 1)      │
└──────────────────┘              └──────────────────┘
                                    其他模块看不见内部定义

9.2 define-module

9.2.1 基本语法

;; 最简单的模块定义
(define-module (my-module))

;; 导出特定绑定
(define-module (my-module)
  #:export (greet farewell))

(define (greet name)
  (string-append "Hello, " name "!"))

(define (farewell name)
  (string-append "Goodbye, " name "!"))

;; 内部辅助函数(不导出)
(define (internal-helper x)
  (* x x))

;; 使用模块
;; (use-modules (my-module))
;; (greet "World")  ; => "Hello, World!"

9.2.2 模块路径与命名

模块名称使用列表形式,对应文件系统的路径结构:

模块名文件路径
(my-module)my-module.scm
(my utils)my/utils.scm
(app core helpers)app/core/helpers.scm
(lib (my module))lib/my/module.scm
;; 文件: src/utils/math.scm
(define-module (utils math)
  #:export (square cube factorial))

(define (square x) (* x x))
(define (cube x) (* x x x))
(define (factorial n)
  (if (<= n 1) 1 (* n (factorial (- n 1)))))

9.2.3 模块声明选项

(define-module (my-module)
  ;; 导出
  #:export (public-func public-var)

  ;; 选择性使用(替换绑定)
  #:use-module (srfi srfi-1)
  #:use-module ((srfi srfi-13) #:select (string-join string-split))
  #:use-module ((srfi srfi-13) #:prefix s13-)

  ;; 替换现有绑定
  #:replace (my-+)

  ;; 重导出
  #:re-export (map filter)

  ;; 自动导入列表
  #:autoload (ice-9 format) (format)
  )

9.3 use-modules

9.3.1 基本导入

;; 简单导入
(use-modules (srfi srfi-1))
;; 现在可以使用 srfi-1 的所有导出函数
(filter even? '(1 2 3 4))  ; => (2 4 6)

;; 导入多个模块
(use-modules (srfi srfi-1)
             (srfi srfi-13)
             (ice-9 format))

;; 选择性导入(只导入需要的函数)
(use-modules ((srfi srfi-1) #:select (filter fold)))

;; 导入带前缀(避免冲突)
(use-modules ((srfi srfi-13) #:prefix s13/))
(s13/string-join '("a" "b" "c") "-")  ; => "a-b-c"

;; 除了某些函数外全部导入
(use-modules ((srfi srfi-1) #:except (map)))
;; 使用 Guile 内置的 map,不用 srfi-1 的

9.3.2 导入对比

导入方式语法适用场景
全部导入(use-modules (mod))不担心冲突时
选择性导入(use-modules ((mod) #:select (...)))只需少量函数
前缀导入(use-modules ((mod) #:prefix p/))避免命名冲突
排除导入(use-modules ((mod) #:except (...)))有少量冲突

9.4 导出与接口

9.4.1 #:export 详解

(define-module (my-api)
  #:export (
    ;; 导出函数
    process-data
    validate-input

    ;; 导出变量
    *default-timeout*
    *version*

    ;; 导出宏
    with-config

    ;; 导出记录类型(构造函数和谓词)
    make-user user?
    user-name user-email
  ))

(define *version* "1.0.0")
(define *default-timeout* 30)

(define (process-data data)
  ;; 处理数据
  data)

(define (validate-input input)
  (and (string? input) (not (string-null? input))))

(define-syntax with-config
  (syntax-rules ()
    ((_ config body ...)
     (parameterize ((*current-config* config))
       body ...))))

(define-record-type <user>
  (make-user name email)
  user?
  (name user-name)
  (email user-email))

9.4.2 re-export(重导出)

;; 重导出允许用户通过一个模块访问多个模块的功能
(define-module (my-utils)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-13)
  #:use-module (ice-9 format)

  ;; 重导出 srfi-1 的常用函数
  #:re-export (map filter fold append reverse)

  ;; 重导出 srfi-13 的常用函数
  #:re-export (string-join string-split string-trim)

  ;; 重导出 format
  #:re-export (format)

  ;; 自己的函数
  #:export (greet))

;; 用户只需 (use-modules (my-utils)) 即可访问所有函数

9.5 模块内部机制

9.5.1 模块与变量

;; 每个模块都有自己的变量命名空间
;; 模块变量通过 (module-variable ...) 访问

;; 在 REPL 中查看模块
(module-map (lambda (sym var)
              (format #t "~a: ~a~%" sym
                      (if (variable-bound? var)
                          (variable-ref var)
                          "*unbound*")))
            (resolve-module '(guile-user)))

;; 访问其他模块的变量
(module-ref (resolve-module '(srfi srfi-1)) 'filter)
;; => #<procedure filter (pred list)>

;; 动态导入
(module-use! (current-module)
             (resolve-interface '(srfi srfi-1)))

9.5.2 模块层次结构

scheme
├── (guile)              ;; 核心模块
├── (guile-user)         ;; REPL 默认模块
├── (srfi srfi-1)        ;; 列表库
├── (srfi srfi-13)       ;; 字符串库
├── (ice-9 format)       ;; 格式化
├── (ice-9 regex)        ;; 正则表达式
├── (ice-9 threads)      ;; 线程
├── (ice-9 popen)        ;; 进程管道
└── ...

9.6 实战项目结构

9.6.1 完整项目示例

my-project/
├── my-project/
│   ├── core.scm          ;; (my-project core)
│   ├── utils.scm         ;; (my-project utils)
│   ├── db/
│   │   ├── connection.scm ;; (my-project db connection)
│   │   └── query.scm     ;; (my-project db query)
│   └── web/
│       ├── handler.scm    ;; (my-project web handler)
│       └── middleware.scm ;; (my-project web middleware)
└── main.scm
;; my-project/core.scm
(define-module (my-project core)
  #:use-module (srfi srfi-1)
  #:use-module (ice-9 format)
  #:export (app-version
            log-info
            log-error
            with-error-handling))

(define *app-version* "0.1.0")

(define (app-version) *app-version*)

(define (log-info msg . args)
  (apply format #t (string-append "[INFO] " msg "~%") args))

(define (log-error msg . args)
  (apply format #t (string-append "[ERROR] " msg "~%") args))

(define-syntax with-error-handling
  (syntax-rules ()
    ((_ body ...)
     (catch #t
       (lambda () body ...)
       (lambda (key . args)
         (log-error "~a: ~a" key args)
         #f)))))
;; my-project/utils.scm
(define-module (my-project utils)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-13)
  #:export (string-blank?
            string-present?
            alist-merge
            filter-nil))

(define (string-blank? s)
  (or (not s) (string-null? (string-trim-both s))))

(define (string-present? s)
  (not (string-blank? s)))

(define (alist-merge . alists)
  (fold (lambda (a acc)
          (fold (lambda (pair acc)
                  (acons (car pair) (cdr pair) acc))
                acc a))
        '()
        alists))

(define (filter-nil alist)
  (filter (lambda (pair) (cdr pair)) alist))
;; main.scm
#!/usr/bin/env guile
!#
(add-to-load-path (dirname (current-filename)))

(use-modules (my-project core)
             (my-project utils))

(define (main args)
  (format #t "My Project v~a~%" (app-version))
  (log-info "启动完成")
  0)

(main (command-line))

9.7 公共 API 设计

9.7.1 接口封装原则

;; 良好的公共 API 设计
(define-module (my-lib)
  ;; 1. 明确导出列表
  #:export (
    ;; 构造函数
    make-config
    ;; 谓词
    config?
    ;; 访问器
    config-host
    config-port
    ;; 核心功能
    start-server
    stop-server
  ))

;; 2. 内部实现不导出
(define (validate-config config)
  ;; 内部验证逻辑
  ...)

(define (internal-start config)
  ;; 内部启动逻辑
  ...)

;; 3. 公共函数做输入验证
(define (start-server config)
  (unless (config? config)
    (error "无效的配置" config))
  (validate-config config)
  (internal-start config))

9.7.2 版本兼容

;; 使用版本号管理 API 兼容性
(define-module (my-lib)
  #:export (*lib-version*
            make-thing
            thing?
            thing-value))

(define *lib-version* "2.1.0")

;; 弃用函数(保留兼容性,但发出警告)
(define (old-make-thing value)
  (issue-deprecation-warning
    "old-make-thing 已弃用,请使用 make-thing")
  (make-thing value))

9.8 动态模块加载

;; 动态加载模块
(let ((mod (resolve-interface '(srfi srfi-1))))
  (module-ref mod 'filter))

;; 条件加载
(define (try-load-module module-name)
  (catch 'misc-error
    (lambda ()
      (let ((mod (resolve-interface module-name)))
        (module-use! (current-module) mod)
        #t))
    (lambda (key . args)
      (format #t "警告: 模块 ~a 不可用~%" module-name)
      #f)))

(try-load-module '(srfi srfi-64))  ; 测试框架(可选)

9.9 本章小结

主题要点
define-module定义模块及其公共接口
use-modules导入其他模块的功能
#:export控制模块导出的绑定
#:select/#:prefix选择性导入和前缀导入
#:re-export重导出其他模块的函数
项目结构模块名对应文件路径

扩展阅读


上一章:第 8 章:数据结构 下一章:第 10 章:输入输出