tutorial. 算术解释器与简化器[lp-0003]
算术解释器定义表达式的值,简化器改变表达式结构。LP让两种程序与它们的解释、示例和性质核验来自同一份.plant。代码使用文章内定义,检查值、有限样本和异常分别对应三种核验方式。
表达式只有整数、变量、加法与乘法。例如(* (+ x 0) 1);环境是变量与数值组成的association list。
program. 算术表达式解释器 interpret 的定义[lp-eval]
(define (lookup variable environment)
;; 不把未绑定的变量默认为零。
(let ((entry (assq variable environment)))
(if entry (cadr entry) (throw 'unbound-name variable))))
(define (interpret expression environment)
(match expression
((? integer? n) n)
((? symbol? name) (lookup name environment))
(('+ left right)
(+ (interpret left environment) (interpret right environment)))
(('* left right)
(* (interpret left environment) (interpret right environment)))
(_ (throw 'invalid-expression expression))))example. 算术 interpret 的环境求值结果为 20[lp-eval-test]
(interpret '(* (+ x 2) y) '((x 3) (y 4)))⇒ 20 · checked
example. 算术变量 missing 的未绑定异常[lp-unbound]
(interpret 'missing '())
⇒ "expected exception: unbound-name" · checked
example. 算术减法表达式的未知语法异常[lp-invalid]
(interpret '(- 1 2) '())
⇒ "expected exception: invalid-expression" · checked
解释器把语言允许的输入限定下来。重写规则应该保留这个语义,而不只是让显示出来的表达式更短。
program. 算术简化的局部 rewrite 规则[lp-rewrite]
(define (rewrite operation left right)
(cond
((and (integer? left) (integer? right))
((if (eq? operation '+) + *) left right))
((and (eq? operation '+) (equal? left 0)) right)
((and (eq? operation '+) (equal? right 0)) left)
((and (eq? operation '*) (equal? left 1)) right)
((and (eq? operation '*) (equal? right 1)) left)
(else (list operation left right))))算术重写规则不使用(* 0 x) → 0。如果x没有绑定,原表达式会报错,改写后却得到零:这会改变错误语义。保留这个边界比增加一条看起来显然的规则更重要。
program. 算术简化器 simplify 的递归定义[lp-simplify]
(define (simplify expression)
(match expression
((? integer? n) n)
((? symbol? name) name)
((operation left right)
(if (memq operation '(+ *))
(rewrite operation (simplify left) (simplify right))
(throw 'invalid-expression expression)))
(_ (throw 'invalid-expression expression))))简化器调用局部规则,普通Scheme binding承担程序组合。代码tree的链接负责解释的导航。
example. 算术 simplify 的结果为 x[lp-simplify-test]
(simplify '(* (+ x 0) (+ 1 0)))⇒ x · checked
example. 算术零乘法保留 unbound-name 异常[lp-zero-error]
(interpret (simplify '(* 0 missing)) '())
⇒ "expected exception: unbound-name" · checked
算术性质检查使用有限枚举,样本深度与环境显式固定,构建时可复现。结果保持检查简化前后的值,幂等性检查重复简化得到的结构。
program. 算术性质检查的表达式与环境样本[lp-cases]
(define (expressions depth)
(let ((atoms '(-1 0 1 x y)))
(if (= depth 0) atoms
(let ((smaller (expressions (- depth 1))))
(append atoms
(append-map
(lambda (operation)
(append-map
(lambda (left)
(map (lambda (right) (list operation left right)) smaller))
smaller))
'(+ *)))))))
(define environments
'(((x -2) (y 3)) ((x 0) (y 0)) ((x 4) (y -1))))
(define cases
(append-map
(lambda (expression)
(map (lambda (environment) (list expression environment)) environments))
(expressions 2)))example. 算术简化的 18165 项结果保持核验[lp-preserve]
(lambda (sample)
(let ((expression (car sample)) (environment (cadr sample)))
(= (interpret expression environment)
(interpret (simplify expression) environment))))
⇒ "18165 cases passed" · checked
example. 算术 simplify 的幂等性核验[lp-idempotent]
(lambda (expression)
(equal? (simplify expression) (simplify (simplify expression))))
⇒ "6055 cases passed" · checked
这分别检查18,165组表达式与环境,以及6,055个表达式。有限枚举是证据,不是对所有表达式的证明;未绑定变量和错误语义由unbound-name与invalid-expression异常案例另外检查。
算术核验的模块作用域与构建结果[#37]
checked-property遇到不满足谓词的样本就中止构建,并报告property ID与反例;checked-error只接受指定的异常key,其他异常继续向外传播,没有抛出异常同样失败。
本文同时定义(flora expression-language)模块,导出interpret与simplify。另一篇文章transclude 简化器只会展示代码;调用binding则需显式导入这个模块。模块导入教程实际使用这些定义,无须另外维护一份.scm。
确定性的核验结果可以随内容IR缓存。grove check会重新求值文章;正常build可能复用输入未变的结果。如果计算读取额外文件,要调用depend-on! 登记依赖;读取时间、随机数或外部状态的实验应调用disable-cache!。checked表示读取时核验通过,不表示浏览器中实时运行。