Guile/Scheme 编程教程 / 第12章:最佳实践
第 12 章:最佳实践
12.1 Guile 编程风格
12.1.1 命名规范
;; 1. 变量和函数:使用连字符分隔(kebab-case)
(define user-name "Alice") ; ✓ 正确
(define user_name "Alice") ; ✗ 不推荐(C 风格)
(define userName "Alice") ; ✗ 不推荐(驼峰式)
;; 2. 谓词函数:以 ? 结尾
(define (valid? input) ...) ; ✓ 正确
(define (is-valid input) ...) ; 不推荐(Scheme 不用 is- 前缀)
;; 3. 修改函数:以 ! 结尾(破坏性修改)
(define (vector-set! vec i val) ...) ; ✓ 正确
(define (hash-set! ht key val) ...) ; ✓ 正确
;; 4. 转换函数:类型 → 类型
(define (list->vector lst) ...) ; ✓ 正确
(define (string->number str) ...) ; ✓ 正确
;; 5. 常量:使用 * 包围
(define *max-retries* 3) ; ✓ 正确
(define *default-timeout* 30) ; ✓ 正确
;; 6. 内部辅助函数:使用 % 前缀
(define %internal-helper ...) ; ✓ 正确(模块内部用)
;; 7. 记录类型:<类型名>
(define-record-type <user> ...) ; ✓ 正确
12.1.2 代码格式化
;; 1. 括号位置:闭括号放在同一行
(define (greet name) ; ✓ 正确
(string-append "Hello, " name "!"))
(define (greet name) ; ✗ 不推荐
(string-append "Hello, " name "!"
)
)
;; 2. 缩进:2 个空格
(define (process-items items)
(let loop ((rest items) ; 2 空格缩进
(acc '()))
(if (null? rest)
(reverse acc)
(loop (cdr rest)
(cons (process (car rest)) acc)))))
;; 3. 长表达式换行
(define (long-function arg1 arg2 arg3
arg4 arg5)
(let ((result (compute arg1 arg2 arg3
arg4 arg5)))
result))
;; 4. cond 子句格式
(define (classify x)
(cond
((number? x) "数字") ; 每个子句 2 空格
((string? x) "字符串")
((symbol? x) "符号")
(else "未知")))
;; 5. let 绑定对齐
(let ((name "Alice") ; 绑定对齐
(age 30)
(city "Beijing"))
(format #t "~a, ~a, ~a" name age city))
12.1.3 注释规范
;;; 文件头注释(4 个分号)
;;; my-module.scm — 用户管理模块
;;; Copyright (C) 2026 My Project
;;; 章节标题(3 个分号)
;;; ===== 用户操作 =====
;; 段落注释(2 个分号)—— 最常用
;; 这个函数处理用户输入并验证格式
(define (validate-user input)
...)
; 行尾注释(1 个分号)
(define timeout 30) ; 超时秒数
;;; 文档注释(3 个分号,放在定义前面)
;;; create-user — 创建新用户
;;; 参数 name: 用户名(字符串)
;;; 参数 email: 邮箱地址(字符串)
;;; 返回: <user> 记录
;;; 异常: 当 name 为空时抛出 'invalid-input
(define (create-user name email)
(when (string-blank? name)
(throw 'invalid-input "用户名不能为空"))
(make-user name email))
12.2 代码组织
12.2.1 文件结构模板
;;; -*- mode: scheme; -*-
;;; filename.scm — 模块简述
;;;
;;; 详细描述...
;;; Copyright (C) 2026 Author <email@example.com>
;;; License: GPL-3.0+
;; 模块声明
(define-module (my-module)
#:use-module (srfi srfi-1)
#:use-module (ice-9 format)
#:export (public-func
public-var))
;; 常量定义
(define *version* "1.0.0")
;; 记录类型定义
(define-record-type <widget>
(make-widget name size)
widget?
(name widget-name)
(size widget-size))
;; 内部辅助函数
(define (%helper x)
...)
;; 公共函数
(define (public-func x)
...)
;; 主逻辑(如果是脚本)
(define (main args)
...)
12.2.2 依赖管理
;; 在模块顶部声明所有依赖
(define-module (my-module)
;; 标准库
#:use-module (ice-9 format)
#:use-module (ice-9 regex)
;; SRFI
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-9)
;; 第三方库
#:use-module (json)
;; 项目内部模块
#:use-module (my-project utils)
#:use-module (my-project config)
;; 选择性导入
#:use-module ((srfi srfi-13)
#:select (string-join string-split))
#:use-module ((srfi srfi-13)
#:prefix s13/)
;; 导出
#:export (my-func my-var))
12.3 性能优化
12.3.1 性能分析工具
;; 1. 使用 time 宏测量
(define-syntax time-it
(syntax-rules ()
((_ label body ...)
(let ((start (get-internal-real-time)))
(let ((result (begin body ...)))
(let ((end (get-internal-real-time)))
(format #t "[~a] 耗时: ~a ms~%"
label
(/ (* (- end start) 1000)
internal-time-units-per-second))
result))))))
;; 2. 使用 Guile 内置的统计
(use-modules (statprof))
(statprof-reset)
(statprof-start)
;; 要分析的代码
(let loop ((i 0))
(when (< i 10000)
(some-computation i)
(loop (+ i 1))))
(statprof-stop)
(statprof-display)
;; 显示每个函数的耗时占比
;; 3. 追踪函数调用
(trace some-function)
(some-function arg1 arg2)
;; 输出每次调用和返回值
(untrace some-function)
12.3.2 性能优化策略
;; 策略 1: 使用尾递归避免栈溢出
;; 差:
(define (sum-bad lst)
(if (null? lst) 0
(+ (car lst) (sum-bad (cdr lst)))))
;; 好:
(define (sum-good lst)
(let loop ((rest lst) (acc 0))
(if (null? rest) acc
(loop (cdr rest) (+ (car rest) acc)))))
;; 策略 2: 使用数值向量替代列表
;; 差:列表存储数值
(define data-list (iota 1000000)) ; 列表,内存不连续
;; 好:使用 SRFI-4 数值向量
(use-modules (srfi srfi-4))
(define data-vec (apply f64vector (map inexact (iota 1000000))))
;; 策略 3: 使用哈希表替代关联列表
;; 差:大列表查找 O(n)
(define big-alist (map (lambda (i) (cons i (* i i))) (iota 10000)))
;; 好:哈希表查找 O(1)
(define big-ht (make-hash-table))
(for-each (lambda (i) (hash-set! big-ht i (* i i))) (iota 10000))
;; 策略 4: 避免不必要的分配
;; 差:
(define (process items)
(map (lambda (x) (* x 2)) ; 创建新列表
(filter even? items))) ; 又创建一个新列表
;; 好:单次遍历
(define (process items)
(let loop ((rest items) (acc '()))
(cond
((null? rest) (reverse acc))
((even? (car rest))
(loop (cdr rest) (cons (* (car rest) 2) acc)))
(else (loop (cdr rest) acc)))))
;; 策略 5: 缓存重复计算
(define memo-fib
(let ((cache (make-hash-table)))
(lambda (n)
(or (hash-ref cache n)
(let ((result (if (<= n 1) n
(+ (memo-fib (- n 1))
(memo-fib (- n 2))))))
(hash-set! cache n result)
result)))))
12.3.3 编译优化
# 编译 Guile 代码以提升性能
guild compile my-module.scm
# 指定优化级别
guild compile -O2 my-module.scm
# 批量编译整个目录
find src/ -name "*.scm" -exec guild compile {} \;
# 查看编译产物
ls src/*.go # 编译后的字节码文件
;; 在代码中控制编译
(use-modules (system base compile))
;; 编译表达式
(compile '(+ 1 2) #:to 'value)
;; 编译文件
(compile-file "my-module.scm" #:to 'value)
;; 在模块中启用编译
(define-module (my-module)
#:declarative? #t ; 声明式模块,可优化
#:autoload ...)
12.4 调试技巧
12.4.1 REPL 调试
;; 1. 使用 backtrace 查看调用栈
(catch #t
(lambda ()
(error-provoking-code))
(lambda (key . args)
(backtrace) ; 打印完整调用栈
(display key)))
;; 2. 使用 ,bt 在 REPL 中查看
;; scheme@(guile-user)> ,bt
;; 3. 断点式调试
(define (debug-break)
(let ((port (current-error-port)))
(format port "~%=== 调试断点 ===~%")
(format port "输入 (continue) 继续执行~%")
(break)))
;; 4. 使用 geiser 的 M-x geiser-edit-module 查看模块源码
12.4.2 日志记录
;; 简易日志框架
(define *log-level* (make-parameter 'info))
(define *log-port* (make-parameter (current-error-port)))
(define (log-message level fmt . args)
(when (>= (log-level->number level)
(log-level->number (*log-level*)))
(apply format (*log-port*)
(string-append "[" (symbol->string level) "] " fmt "~%")
args)))
(define (log-level->number level)
(case level
((debug) 0) ((info) 1) ((warn) 2) ((error) 3)
(else 1)))
(define (log-debug fmt . args) (apply log-message 'debug fmt args))
(define (log-info fmt . args) (apply log-message 'info fmt args))
(define (log-warn fmt . args) (apply log-message 'warn fmt args))
(define (log-error fmt . args) (apply log-message 'error fmt args))
;; 使用
(parameterize ((*log-level* 'debug))
(log-debug "调试信息: ~a" value)
(log-info "处理完成")
(log-error "出错了: ~a" error-msg))
12.4.3 单元测试
;; 使用 SRFI-64 测试框架
(use-modules (srfi srfi-64))
(test-begin "用户模块测试")
(test-group "创建用户"
(test-assert "有效用户" (make-user "Alice" 30))
(test-eq "用户名" "Alice" (user-name (make-user "Alice" 30)))
(test-eq "年龄" 30 (user-age (make-user "Alice" 30))))
(test-group "验证"
(test-assert "有效输入" (validate-input "hello"))
(test-assert "空输入无效" (not (validate-input "")))
(test-assert "非字符串无效" (not (validate-input 42))))
(test-end "用户模块测试")
;; 测试辅助宏
(define-syntax test-suite
(syntax-rules ()
((_ name test ...)
(begin
(test-begin name)
test ...
(test-end name)))))
(define-syntax test-macro-expansion
(syntax-rules ()
((_ macro expected)
(test-equal (quote macro)
(quote expected)
(syntax->datum (expand (quote macro)))))))
;; 使用
(test-suite "数学函数测试"
(test-eq "平方" 25 (square 5))
(test-eq "立方" 27 (cube 3))
(test-approximate "平方根" 1.414 (sqrt 2) 0.001))
12.5 Guix 系统配置
12.5.1 Guix 系统配置基础
;; /etc/config.scm — 完整的 Guix 系统配置
(use-modules (gnu)
(gnu system)
(gnu system nss)
(gnu packages)
(gnu packages admin)
(gnu packages guile)
(gnu packages version-control)
(gnu packages networking)
(gnu services)
(gnu services ssh)
(gnu services networking)
(gnu services web))
(operating-system
(host-name "guile-server")
(timezone "Asia/Shanghai")
(locale "zh_CN.utf8")
;; 键盘布局
(keyboard-layout (keyboard-layout "cn"))
;; 引导加载器
(bootloader (bootloader-configuration
(bootloader grub-bootloader)
(targets '("/dev/sda"))
(keyboard-layout keyboard-layout)))
;; 文件系统
(file-systems (append
(list (file-system
(device (file-system-label "root"))
(mount-point "/")
(type "ext4")))
%base-file-systems))
;; 用户账户
(users (cons (user-account
(name "developer")
(comment "Guile 开发者")
(group "users")
(home-directory "/home/developer")
(supplementary-groups '("wheel" "netdev" "audio" "video")))
%base-user-accounts))
;; 系统级包
(packages (append (list guile-3.0
git
nss-certs
inetd)
%base-packages))
;; 服务
(services (append
(list (service openssh-service-type
(openssh-configuration
(port-number 22)
(permit-root-login #f)))
(service dhcp-client-service-type))
%base-services)))
12.5.2 自定义 Guix 服务
;; 定义自定义服务
(use-modules (gnu services)
(gnu services shepherd)
(gnu system shadow)
(guix gexp)
(guix records))
;; 服务配置记录
(define-record-type <my-app-config>
(make-my-app-config port log-file data-dir)
my-app-config?
(port my-app-config-port)
(log-file my-app-config-log-file)
(data-dir my-app-config-data-dir))
;; 默认配置
(define %default-my-app-config
(make-my-app-config 8080 "/var/log/my-app.log" "/var/lib/my-app"))
;; Shepherd 服务定义
(define (my-app-shepherd-service config)
(list (shepherd-service
(provision '(my-app))
(requirement '(networking))
(documentation "My Guile Web App")
(start #~(make-forkexec-constructor
(list #$(file-append guile-3.0 "/bin/guile")
"-e" "main"
#$(local-file "../app/main.scm"))
#:environment-variables
(list (string-append "PORT="
(number->string
#$(my-app-config-port config))))))
(stop #~(make-kill-destructor)))))
;; 服务类型
(define my-app-service-type
(service-type
(name 'my-app)
(extensions
(list (service-extension shepherd-root-service-type
my-app-shepherd-service)))
(default-value %default-my-app-config)
(description "运行 Guile Web 应用")))
12.5.3 Guix 包定义
;; 定义自定义 Guix 包
(use-modules (guix packages)
(guix build-system gnu)
(guix licenses)
(guix gexp)
(guix git-download)
(gnu packages guile))
(define-public my-guile-lib
(package
(name "my-guile-lib")
(version "1.0.0")
(source (origin
(method git-fetch)
(uri (git-reference
(url "https://github.com/user/my-guile-lib")
(commit (string-append "v" version))))
(sha256
(base32 "0abc..."))))
(build-system gnu-build-system)
(arguments
'(#:phases
(modify-phases %standard-phases
(add-after 'install 'wrap-program
(lambda* (#:key outputs #:allow-other-keys)
(let ((out (assoc-ref outputs "out")))
(wrap-program (string-append out "/bin/my-app")
`("GUILE_LOAD_PATH" =
(,(string-append out "/share/guile/site/3.0"))))))))))
(native-inputs
(list guile-3.0))
(home-page "https://github.com/user/my-guile-lib")
(description "我的 Guile 工具库")
(license gpl3+)))
12.6 项目模板
12.6.1 推荐的项目结构
my-guile-project/
├── .gitignore
├── LICENSE
├── README.md
├── Makefile
├── guix.scm # Guix 环境定义
├── manifest.scm # Guix 依赖清单
├── configure.ac # 可选:autotools 配置
├── my-project/
│ ├── core.scm
│ ├── utils.scm
│ ├── web/
│ │ ├── handler.scm
│ │ └── middleware.scm
│ └── db/
│ ├── connection.scm
│ └── query.scm
├── tests/
│ ├── test-core.scm
│ ├── test-utils.scm
│ └── test-web.scm
├── scripts/
│ └── run.sh
└── doc/
└── api.md
12.6.2 项目 Makefile 模板
# Makefile for Guile Project
GUILE = guile
GUILEC = guild compile
GUILD = guild
TEST_RUNNER = $(GUILE) -L . -e main
# 源码目录
SRC_DIR = my-project
TEST_DIR = tests
# 源码文件
SOURCES = $(shell find $(SRC_DIR) -name "*.scm")
COMPILED = $(SOURCES:.scm=.go)
# 默认目标
.PHONY: all build test clean install
all: build
# 编译
build: $(COMPILED)
%.go: %.scm
$(GUILEC) -L $(SRC_DIR) $<
# 测试
test:
$(GUILE) -L $(SRC_DIR) $(TEST_DIR)/test-core.scm
$(GUILE) -L $(SRC_DIR) $(TEST_DIR)/test-utils.scm
# 清理
clean:
find . -name "*.go" -delete
rm -rf public/
# 安装
install:
$(GUILD) compile $(SRC_DIR)
mkdir -p $(DESTDIR)/share/guile/site/3.0
cp -r $(SRC_DIR) $(DESTDIR)/share/guile/site/3.0/
# 开发环境
dev:
guix shell -m manifest.scm
# 文档
doc:
guix shell texinfo -- makeinfo doc/api.texi
12.6.3 .gitignore 模板
# 编译产物
*.go
*.bak
# Guix
.guix-profile
# 编辑器
*~
\#*\#
.dir-locals.el
.vscode/
# 系统
.DS_Store
Thumbs.db
# 构建
/public/
/build/
/dist/
12.7 学习路径建议
12.7.1 初学者路径
第1周: 第1-3章(概述、安装、基本语法)
→ 熟悉 REPL,理解 S-表达式和求值规则
第2周: 第4-5章(列表、函数)
→ 掌握列表操作、lambda 和闭包
第3周: 第6章(控制流)
→ 条件、绑定、递归、尾调用
第4周: 第8章(数据结构)
→ 记录、向量、哈希表
实践: 用 Guile 写一个小工具脚本
12.7.2 进阶路径
第5-6周: 第7章(宏系统)
→ syntax-rules、define-macro
第7-8周: 第9-10章(模块系统、I/O)
→ 项目组织、文件操作、网络
实践: 开发一个有多个模块的库
12.7.3 高级路径
第9-10周: 第11-12章(C扩展、最佳实践)
→ FFI、性能优化、Guix 配置
实践:
1. 封装一个 C 库
2. 编写 Guix 包定义
3. 为开源项目贡献 Guile 代码
12.8 推荐资源
12.9 本章小结
| 主题 | 要点 |
|---|
| 命名规范 | kebab-case、?/! 后缀、常量 |
| 代码格式 | 括号同行、2 空格缩进 |
| 性能优化 | 尾递归、数值向量、哈希表、JIT |
| 调试 | REPL 命令、statprof、SRFI-64 测试 |
| Guix 配置 | 系统定义、服务定义、包定义 |
| 项目模板 | 标准目录结构和构建脚本 |
扩展阅读
上一章:第 11 章:C 扩展与 FFI
返回:教程目录