Racket until特殊形式转换函数until->transformed实现疑问
实现步骤说明
1. 修正函数签名的常见误区
原写法中(define (until->transformed exp1 ... expn)) 无法正常工作:Racket 中普通函数的形参列表里不能直接使用...(这是宏模板的专属语法),要接收任意数量的可变输入参数,必须通过点号绑定剩余参数到列表上,正确的签名应该是:
(define (until->transformed . exps) ; 函数体逻辑 )
这里的exps 会接收到待转换的until表达式中、除了until关键字之外的所有参数,对应结构就是(list exp1 exp2 ... expn test)。
2. 拆分输入参数
根据until的语法规则,输入的参数列表可以拆成两部分:
- 所有位于列表倒数第二位及之前的表达式,是循环中需要顺序执行的循环体内容
- 列表的最后一个元素,是用来判断是否终止循环的测试条件
test
在函数体中可以直接用内置函数拆分:
; 提取循环体:去掉列表最后1个元素的所有内容 (define loop-body (drop-right exps 1)) ; 提取终止测试:列表的最后一个元素 (define test-expr (last exps))
3. 拼接目标结构的等价表达式
按照要求的目标结构,逐部分用s表达式构造函数返回值:
目标结构的核心组成是:
- 最外层是无绑定列表的
let表达式 let的第一个体表达式是内部定义的无参递归函数looploop的函数体先放置所有提取到的循环体表达式- 循环体最后追加判断逻辑:如果
test-expr为真就返回(void),否则递归调用loop完成循环
let的第二个体表达式是对loop的初次调用,启动整个循环流程
拼接完成的完整函数体如下:
(define (until->transformed . exps) (define loop-body (drop-right exps 1)) (define test-expr (last exps)) ; 构造loop函数的完整体:循环体 + 终止判断分支 (define loop-body-seq (append loop-body (list (list 'if test-expr '(void) (list 'loop))))) ; 构造loop函数的定义 (define loop-def (list 'define (list 'loop) loop-body-seq)) ; 返回整个等价的转换表达式 (list 'let (list) loop-def (list 'loop)) )
4. 验证转换效果
举个实际调用的例子,当我们传入待转换的until表达式:
(until (displayln "loop step") (set! i (add1 i)) (< i 5))
调用until->transformed后会生成完全符合要求的等价代码:
(let () (define (loop) (displayln "loop step") (set! i (add1 i)) (if (< i 5) (void) (loop))) (loop))
这个生成的表达式的执行逻辑和原until特殊形式完全一致:顺序执行循环体、判断终止条件、条件为假时重复循环、条件为真时终止并返回#<void>。
注意:构造s表达式时不要把测试条件写死成静态的
test符号,一定要把拆分出来的实际测试表达式嵌到if分支里,否则生成的代码会永远判断符号test的真假,和原逻辑完全脱节。
内容的提问来源于stack exchange,提问作者Svetochka
相关产品推荐
相关产品推荐

