You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Lisp中实现无副作用的OR宏函数

解决Lisp OR宏中表达式重复求值的问题

你遇到的问题本质上是宏编写中常见的重复求值陷阱——你的or2宏在if语句里两次引用了test表达式,导致它被执行了多次(比如(print 1)会输出多次),而原生的or宏只会对每个表达式求值一次,一旦遇到真值就停止后续计算。

问题根源分析

先看你的宏展开后的代码,当调用(or2 (print 1) 2)时,宏会展开成:

(if (print 1) (print 1) (if (list 2) (or2 2) nil))

这里(print 1)在if的条件判断和返回值位置各出现一次,所以会被执行两次;加上原代码里(list ,@args)的逻辑问题,还会触发额外的递归调用,导致(print 1)被执行第三次。

解决方案:用临时变量存储求值结果

要避免重复求值,核心思路是把表达式的结果存到一个临时变量里,只求值一次,之后所有判断和返回都用这个变量。为了避免变量名冲突(比如用户代码里已经有同名变量),我们需要用gensym生成唯一的临时变量名。

修正后的or2宏如下:

(defmacro or2 (&rest args)
  (cond
    ;; 没有参数时返回nil,和原生OR一致
    ((null args) nil)
    ;; 只有一个参数时直接返回,避免多余的LET
    ((null (cdr args)) (car args))
    ;; 多个参数时,用临时变量存储第一个参数的结果
    (t (let ((temp-var (gensym)))
         `(let ((,temp-var ,(car args)))
            (if ,temp-var
                ,temp-var  ;; 用存储的结果,不再重复求值
                (or2 ,@(cdr args))))))))

工作原理详解

  1. gensym生成唯一变量:gensym会生成一个像#:G1234这样的唯一符号,不会和用户代码里的任何变量名冲突,避免了变量捕获问题。
  2. 单次求值存储:把第一个参数(car args)求值后存入temp-var,之后的if判断和返回都用这个变量,确保原表达式只执行一次。
  3. 递归处理剩余参数:如果第一个参数为假,就递归调用or2处理剩下的参数,和原生or的短路逻辑完全一致。

测试验证

现在调用(or2 (print 1) 2),宏会展开成类似这样的代码:

(let ((#:G1234 (print 1)))
  (if #:G1234
      #:G1234
      (or2 2)))

这里(print 1)只会执行一次,输出1,然后返回1,和原生(or (print 1) 2)的行为完全一致。

内容的提问来源于stack exchange,提问作者Jonath P

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:14:50