关于《Racket指南》中rotate与shift-to宏的技术疑问
Racket中rotate和shift-to宏的逻辑解析
1. rotate宏的模式匹配与展开
rotate宏的模板是(rotate a c ...),其中...是Racket宏的可变参数匹配符:
a匹配调用时的第一个参数c ...匹配调用时剩下的所有参数(支持0个或多个)
以(rotate 1 2 3)为例:
a绑定为1c ...绑定为2 3(是参数序列,而非列表)
展开阶段,(c ... a)会将c的序列拼接在a之前,生成(2 3 1);(a c ...)则是1拼接c的序列,生成(1 2 3)。最终rotate展开为:
(shift-to (2 3 1) (1 2 3))
2. shift-to宏的模式匹配与映射
shift-to的模板是(shift-to (from0 from ...) (to0 to ...)),同样依赖...处理可变序列:
- 第一个参数列表
(from0 from ...)匹配传入的第一个列表,比如(2 3 1):from0绑定为2from ...绑定为3 1
- 第二个参数列表
(to0 to ...)匹配传入的第二个列表(1 2 3):to0绑定为1to ...绑定为2 3
模板中的(set! to from) ...是重复展开逻辑:会将from ...和to ...中的元素一一对应,生成多个set!表达式。这里from ...是3 1,to ...是2 3,所以展开为(set! 2 3) (set! 3 1)。
最终shift-to展开后的完整代码是:
(let ([tmp 2]) (set! 2 3) (set! 3 1) (set! 1 tmp))
3. 为什么传入shift-to的顺序是(c ... a)在前?
这个顺序是为了避免赋值时的值覆盖问题:
我们要实现左旋转效果:1的值移到3,2移到1,3移到2。如果直接用(a c ...)作为from列表,先把1的值赋给2,会导致原来的2值丢失,无法再传递给3。
而(c ... a)作为from列表的逻辑是:
- 先保存
from0(原始2的值)到临时变量tmp - 依次用
3覆盖2、1覆盖3(这一步用的都是未被修改的原始值) - 最后用
tmp(原始2的值)覆盖1
所有赋值都基于初始值,不会出现值丢失,最终实现正确的左旋转。
完整手动推导流程(以(rotate 1 2 3)为例)
(rotate 1 2 3)匹配(rotate a c ...),a=1,c...=2 3- 展开为
(shift-to (2 3 1) (1 2 3)) (shift-to (2 3 1) (1 2 3))匹配(shift-to (from0 from ...) (to0 to ...)),from0=2,from...=3 1,to0=1,to...=2 3- 展开为
(let ([tmp 2]) (set! 2 3) (set! 3 1) (set! 1 tmp)) - 执行后:
1=2,2=3,3=1,完成左旋转
内容的提问来源于stack exchange,提问作者not_Huyn_Bin
相关产品推荐
相关产品推荐

