【问题标题】:Why does a macro that uses "let" expand differently from one that doesn't?为什么使用“let”的宏与不使用“let”的宏的扩展方式不同?
【发布时间】:2015-05-08 19:01:16
【问题描述】:

我是 Lisp 的新手,正在阅读 Doug Hoyte 的 Let Over Lambda,他在第 3 章介绍了 Paul Graham 的 nif 宏。我正在玩这个并制作了这两个宏:

(defmacro niffy (expr pos zero neg)
  `(cond ((plusp ,expr) ,pos)
         ((zerop ,expr) ,zero)
         (t ,neg)))

(defmacro niffy2 (expr pos zero neg)
  `(let ((x ,expr))
     (cond ((plusp x) ,pos)
           ((zerop x) ,zero
            (t ,neg)))))

当我执行(macroexpand '(niffy2 10 "positive" "zero" "negative")) 时,我得到了我所期望的:(LET ((X 10)) (COND ((PLUSP X) "positive") ((ZEROP X) "zero" (T "negative"))))

但是当我做(macroexpand '(niffy 10 "positive" "zero" "negative")) 时,我只会得到评估表"positive"。这让我很困惑,因为在niffy 中,cond 被反引号,所以我认为这意味着它不会被评估。在没有宏扩展的情况下评估 niffyniffy2 都完全符合我的预期,分别返回正值、零值和负值的“正”、“零”和“负”。

【问题讨论】:

    标签: macros lisp common-lisp let


    【解决方案1】:

    这是因为cond 是一个宏。尝试运行以下命令:

    (macroexpand
     '(cond ((plusp 10) "positive")
            ((zerop 10) "negative")
            (t "zero")))
    

    在 SBCL 中,我得到了这个:

    (IF (PLUSP 10)
        (PROGN "positive")
        (COND ((ZEROP 10) "negative") (T "zero")))
    T
    

    在 CLISP 中,我得到了这个:

    "positive" ;
    T
    

    虽然我可以理解 CLISP 的行为,但这似乎有点不必要,因为在宏展开之后可以更容易地处理优化。

    (请注意,您通常应该更喜欢带有 let 的版本,因为它不会多次评估其参数。)

    【讨论】:

    • 啊,好吧,我正在使用 clisp。那么 clisp 刚刚编译了cond 还是什么?另外,现在我看看我是否做macroexpand-1 niffy 我得到了我期望的行为。
    • 看起来CLISP中cond的宏只是做了一些优化。编译会导致不同的输出,我认为 CLISP 有一个带有字节码输出的编译器。 (顺便说一句,CLISP 已经失效了)
    【解决方案2】:

    您的代码有错误

    (defmacro niffy2 (expr pos zero neg)
      `(let ((x ,expr))
         (cond ((plusp x) ,pos)
               ((zerop x) ,zero              <- missing parenthesis
                (t ,neg)))))                 <- T is not a function
    

    测试:

    (defun test (x)
      (niffy2 x "positive" "zero" "negative"))
    

    编译器抱怨:

    The following function is undefined:
    T which is referenced by TEST
    

    应该是:

    (defmacro niffy2 (expr pos zero neg)
      `(let ((x ,expr))
         (cond ((plusp x) ,pos)
               ((zerop x) ,zero)
               (t ,neg))))
    

    但是当我这样做时 (macroexpand '(niffy 10 "positive" "zero" "negative")) 我只会得到评估形式“positive”。

    由于您要求进行宏扩展,因此无法对其进行评估。它必须是该实现中宏扩展器的效果。

    请注意,MACROEXPAND 是一个迭代过程。表格将被展开。结果形式可以是另一种宏形式。它也将被扩展。然后再次。再次。直到结果形式不是宏形式。请注意,这不会遍历子表单。它严格执行顶层形式的迭代宏扩展。

    使用MACROEXPAND-1

    因此,如果您只想查看宏的效果,请致电MACROEXPAND-1。这个函数只会展开一次。

    CL-USER 23 > (macroexpand-1 '(niffy 10 "positive" "zero" "negative"))
    (COND ((PLUSP 10) "positive")
          ((ZEROP 10) "zero")
          (T "negative"))
    
    CL-USER 24 > (macroexpand-1 '(COND ((PLUSP 10) "positive")
                                       ((ZEROP 10) "zero")
                                       (T "negative")))
    (IF (PLUSP 10)
      (PROGN "positive")
      (IF (ZEROP 10)
         (PROGN "zero")
         (PROGN "negative")))
    T
    

    【讨论】:

    • t 确实不是函数,但这不是错误,因为它没有被用作函数(至少在添加缺少的括号时)。
    • @DietrichEpp:Lisp 系统不会知道括号放错了。它会抱怨函数应用程序。
    • 但作为人类,我们知道代码中只有一个错误:缺少括号。 Lisp 系统是否足够复杂以解决这个问题是另一回事。
    • @DietrichEpp:我不确定有多少人可以通过查看代码轻松找到它。如果稍后 Lisp 系统抱怨。它抱怨函数调用——这就是我提到它的原因。然后必须回过头来找出 T 被用作函数的原因。
    • 对,但是您已经弄清楚实际错误在哪里。虽然值得一提的是 Lisp 系统可能会抱怨 t 不是一个函数,值得解释的是这是由于缺少括号造成的,值得教人们如何从诊断消息中找出错误,但有仍然只有一个错误:缺少括号。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-13
    • 2015-02-16
    • 1970-01-01
    • 2016-12-17
    • 2017-10-14
    • 1970-01-01
    相关资源
    最近更新 更多