文章目录

Scheme 模块:define-module 与 import

发布于 2026-04-04 08:22:03 · 浏览 3 次 · 评论 0 条

Scheme 模块:define-module 与 import

在 Guile Scheme 中组织代码,核心手段是使用模块系统。模块让你把相关函数、变量打包成独立单元,并通过 define-module 声明模块内容,用 import 引入其他模块的功能。掌握这两者,就能写出结构清晰、可复用的 Scheme 程序。


创建自己的模块

定义一个模块,需要使用 define-module 表达式。它必须放在文件顶部(或 REPL 的最开始),格式如下:

(define-module (mylib math utils)
  #:export (add multiply))
  • (mylib math utils) 是模块名,采用层级命名法,类似文件路径。
  • #:export 后列出你希望对外公开的符号(函数、变量等)。未导出的内容仅在模块内部可见。

步骤:创建并导出函数

  1. 新建一个文件,命名为 utils.scm
  2. 写入以下内容:
(define-module (mylib math utils)
  #:export (add multiply))

(define (add a b)
  (+ a b))

(define (multiply a b)
  (* a b))
  1. 保存文件。此时,addmultiply 成为该模块的公共接口。

注意:模块名 (mylib math utils) 并不要求对应真实目录结构,但 Guile 默认会按此路径查找文件(如 mylib/math/utils.scm)。为简化,可将文件放在当前目录,并在启动 Guile 时设置 %load-path


使用其他模块

要使用已定义的模块,需用 import 引入。import 的语法直接指定模块名。

步骤:导入并调用模块函数

  1. 启动 Guile REPL 或新建主程序文件 main.scm
  2. 添加当前目录到模块搜索路径(如果 utils.scm 在当前目录):
(add-to-load-path ".")
  1. 导入模块:
(import (mylib math utils))
  1. 调用导出的函数:
(add 3 5)      ; 返回 8
(multiply 4 6) ; 返回 24

模块命名与文件布局

Guile 根据模块名自动推断文件位置。例如:

  • 模块 (mylib math utils) → 默认查找 mylib/math/utils.scm
  • 模块 (json) → 查找 json.scm

若不想遵循默认路径,可通过 %load-path 手动指定:

(use-modules (ice-9 popen)) ; 先引入必要模块
(add-to-load-path "/path/to/your/modules")
(import (mylib math utils))

控制导出内容

模块内部定义的所有绑定默认私有。只有显式 #:export 的才能被外部访问。

示例:混合公有与私有函数

(define-module (mylib string helpers)
  #:export (safe-capitalize))

(define (internal-helper s)
  (string-downcase s))

(define (safe-capitalize str)
  (if (string? str)
      (string-titlecase (internal-helper str))
      ""))

在此模块中:

  • safe-capitalize 可被外部调用。
  • internal-helper 无法从模块外访问,即使你知道它的名字。

导入策略:选择性与重命名

有时你只想导入部分符号,或避免名称冲突。Guile 提供精细控制。

仅导入特定符号

(import (only (mylib math utils) add))
; 此时 multiply 不可用

重命名导入符号

(import (rename (mylib math utils) (add my-add)))
; 现在用 my-add 调用原 add 函数
(my-add 1 2) ; 返回 3

组合使用

(import (only (rename (srfi srfi-1) (fold fold-left)) fold-left))

常见错误与排查

错误现象 可能原因 解决方法
no code for module (xxx) 模块文件未找到 确认文件名与模块名匹配,检查 %load-path 是否包含文件所在目录
unbound variable: xxx 符号未导出或拼写错误 核对 #:export 列表,确保导入时名称一致
import: invalid module spec import 语法错误 确保模块名用括号包裹,如 (import (a b c)),而非 (import a b c)

实战:构建多文件项目

假设你要开发一个简单计算器,拆分为两个模块。

  1. 创建 ops.scm
(define-module (calc ops)
  #:export (+ - * /))

(define (+ a b) (primitive+ a b))
(define (- a b) (primitive- a b))
(define (* a b) (primitive* a b))
(define (/ a b) (if (zero? b) (error "Divide by zero") (primitive/ a b)))
  1. 创建 ui.scm
(define-module (calc ui)
  #:export (run-calculator))

(import (calc ops)
        (ice-9 format))

(define (run-calculator)
  (format #t "Result: ~a\n" (+ 10 (* 3 4))))
  1. 主程序 app.scm
(add-to-load-path ".")
(import (calc ui))
(run-calculator) ; 输出: Result: 22

运行命令:

guile app.scm

模块初始化与副作用

模块顶层表达式只在首次导入时执行一次。适合做一次性初始化。

(define-module (mylib config)
  #:export (config-data))

(format #t "Loading config...\n") ; 仅打印一次

(define config-data
  '((host . "localhost") (port . 8080)))

多次 import 不会重复执行 format 语句。


使用标准库模块

Guile 自带大量模块,无需额外安装。常用示例:

  • (ice-9 format):提供 format 字符串格式化。
  • (srfi srfi-1):列表处理函数(如 fold, filter)。
  • (rnrs io ports):端口 I/O 操作。

导入标准库:

(import (srfi srfi-1))
(filter even? '(1 2 3 4 5)) ; 返回 (2 4)

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文