tutorial. 文学编程:片段、计划与实例[lp-0001]
累计器的accumulate接受一个增量,返回累计总量。这个小程序把LP的生命周期写完整:片段保存源码,计划声明组合,实例保存状态。页面标记的含义见标题与代码及结果与依赖。
累计器教程的模块导入[#28]
.plant文档使用以下导入;Flora的project.scm已将LP库所在目录加入Guile的模块搜索路径:
@use-modules[(flora commands) (lp core) (lp scribble) (lp forester) (lp checked) (language scribble commands)]
(lp core)管理片段、计划和实例,(lp scribble)提供code-fragment宏,(lp forester)提供阅读tree与输出,(lp checked)提供核验展示。这里只将(flora commands)用于文章命令,程序核心不依赖Flora。
累计器的代码片段写法[#29]
code-fragment返回片段值,不执行其中的define。operations保存累计操作,state保存total的初始值。作者可以围绕操作解释程序,而在装配中明确状态的位置:
@(define operations
(code-fragment "lp-accumulate" "累计器的 accumulate 定义"
#:requires (total)
(define (accumulate delta)
(set! total (+ total delta))
total)))
@(define state
(code-fragment "lp-accumulator-state" "累计器的 total 初始状态"
(define total 0)))fragment-tree才产生阅读内容。它使用片段的原文与稳定ID;requires明确声明操作依赖total,不能代替完整的依赖分析。
program. 累计器的 accumulate 定义[lp-accumulate]
Defines: (accumulate)
Assembled in 累计器的装配计划
Outputs: accumulator.scm
Requires: total
(define (accumulate delta)
(set! total (+ total delta))
total)program. 累计器的 total 初始状态[lp-accumulator-state]
累计器的计划与公开入口[#30]
@(define accumulator
(make-program "lp-accumulator" "累计器的装配计划"
(list state operations) '(accumulate)))
@program-tree[accumulator]make-program保存state、operations的装配顺序,验证重复身份、重复定义、未知导出和显式依赖,不执行初始化。accumulate是公开入口,total是实例内部状态。Module tree显示计划的导出及片段链接:
module. 累计器的装配计划[lp-accumulator]
计划不是一次运行。program-datums返回Scheme数据形式,program-source返回源码字符串;两者都来自计划中的同一份定义。
累计器实例的创建与状态结果[#31]
instantiate-program在新的宿主命名空间中按计划执行定义。每次调用都表示创建独立实例;instance-binding只开放声明的入口:
@(define first-instance (instantiate-program accumulator)) @(define second-instance (instantiate-program accumulator)) @(define add-first (instance-binding first-instance 'accumulate)) @(define add-second (instance-binding second-instance 'accumulate))
example. 累计器两个实例的结果为 (2 5 10)[lp-accumulator-test]
(let ((first (add-first 2)))
(let ((next (add-first 3)))
(list first next (add-second 10))))⇒ (2 5 10) · checked
2和5来自同一个实例的两次调用;10来自另一个total从0开始的实例。checked-example使用equal?核验这个列表,表达式中的let明确调用顺序。阅读或嵌入结果tree不会再次增加总量。
累计器的同源.scm输出[#32]
@program-output["accumulator.scm" accumulator]
Download accumulator.scm
program-output声明构建产物,并生成Download链接。Flora的LP renderer将源码写到build/programs/accumulator.scm;文件包含定义及来源注释,不保存已运行实例的total。
在项目根目录运行以下命令会创建一个独立的累计器,输出2和5:
guile --no-auto-compile -c '(load "build/programs/accumulator.scm") (write (accumulate 2)) (newline) (write (accumulate 3)) (newline)'
program-source仅返回字符串,真正写文件由输出适配器负责;下载和展示不触发instantiate-program。
纯Guile的LP写法[#33]
LP核心不依赖Grove,普通Scheme datum也能声明片段。Guile使用 -L指定LP库所在目录,以下程序的结果为16:
(use-modules (lp core))
(define fragment
(make-fragment "square" "Square"
'((define (square x) (* x x)))))
(define plan (make-program "squares" "Squares" (list fragment) '(square)))
(define instance (instantiate-program plan))
((instance-binding instance 'square) 4)
缺少原文时使用pretty-print。make-fragment的 #:texts和 #:origins可保存原文与来源,#:requires声明依赖。片段只支持完整define;装配使用共同命名空间,不继承片段声明处的词法binding。