如何编写类似car/cadr的链式Lisp函数?以自定义多级eval为例
实现类似
caddr风格的多次eval函数 作为Lisp新手,你注意到car/cdr可以通过caddr这类组合函数实现链式操作,现在想复刻这种风格来实现多次eval的函数,完全可以做到!下面分几种简单的方法来实现,兼顾易用性和理解成本。
一、先实现核心的「n次eval」工具函数
不管是手动定义还是自动生成,先写一个通用的辅助函数,用来执行指定次数的eval:
(defun n-eval (form times) "对form执行times次eval操作,times为0时直接返回form" (if (<= times 0) form (n-eval (eval form) (1- times))))
这个函数的逻辑很简单:如果次数为0,直接返回输入;否则递归调用eval,并把次数减1,直到完成指定次数。
二、手动定义单个函数(适合少量需求)
如果只需要几个固定次数的函数,直接基于n-eval手动定义即可,清晰直观:
; 1次eval:my-eval(名字里1个a对应1次操作) (defun my-eval (form) (n-eval form 1)) ; 2次eval:my-evaal(名字里2个a对应2次操作) (defun my-evaal (form) (n-eval form 2)) ; 3次eval:my-evaaal(名字里3个a对应3次操作) (defun my-evaaal (form) (n-eval form 3))
测试一下你的例子:
; 先绑定*foo*的值为1 (setq *foo* 1) (my-eval '''*foo*) ; 执行1次eval → 返回 '*foo* (my-evaal '''*foo*) ; 执行2次eval → 返回 *foo* (my-evaaal '''*foo*) ; 执行3次eval → 返回 1 (my-evaaal '(+ 2 1)) ; 执行3次eval → 第一次得到3,后续两次eval数字3还是3 → 返回3
三、用宏自动生成函数(适合大量变体)
如果需要很多不同次数的函数,手动写太麻烦,可以用宏批量生成:
(defmacro def-my-eval-func (name eval-count) "自动生成指定次数的eval函数" `(defun ,name (form) (n-eval form ,eval-count)))
然后调用这个宏就能快速生成任意次数的函数:
(def-my-eval-func my-eval 1) (def-my-eval-func my-evaal 2) (def-my-eval-func my-evaaal 3) ; 还可以加更多,比如4次eval: (def-my-eval-func my-evaaaalm 4)
进阶:用宏展开成嵌套eval(更高效率)
如果你追求更高的效率,可以让宏直接展开成嵌套的eval调用(类似caddr被展开成(car (cdr (cdr x)))),而不是运行时递归:
(defun make-nested-eval (form-symbol n) "生成嵌套n次的eval表达式" (if (zerop n) form-symbol (make-nested-eval `(eval ,form-symbol) (1- n)))) (defmacro def-my-eval-macro (name eval-count) "生成展开为嵌套eval的宏" `(defmacro ,name (form) ,(make-nested-eval 'form eval-count)))
生成宏后使用:
(def-my-eval-macro my-eval 1) (def-my-eval-macro my-evaal 2) (def-my-eval-macro my-evaaal 3)
此时(my-evaaal '''*foo*)会在编译时直接展开成(eval (eval (eval '''*foo*))),运行时不需要循环递归,效率更高。
补充:关于你之前的宏实现
你提到之前用宏转换符号名的字符序列递归处理,其实本质和上面的思路类似——都是解析名字里的标记(比如a的数量)来决定操作次数。如果想自动识别任意my-ev...al格式的函数名,需要用到Lisp的符号查找钩子(比如*undefined-function-hook*),但这属于进阶内容,新手阶段先用上面的方法就足够啦。
内容的提问来源于stack exchange,提问作者Bandoos
相关产品推荐
相关产品推荐

