【问题标题】:Defining the defmacro function using only LISP primitives?仅使用 LISP 原语定义 defmacro 函数?
【发布时间】:2011-04-01 22:42:53
【问题描述】:

McCarthy's 基本 S 函数和谓词是 atomeqcarcdrcons

然后他继续添加他的基本符号,以便能够编写他所谓的 S-Function:quotecondlambdalabel

在此基础上,我们将这些称为“LISP 原语”(尽管我对类型谓词如 numberp 持开放态度)

您将如何在您选择的 LISP 中仅使用这些原语来定义 defmacro 函数? (包括 Scheme 和 Clojure)

【问题讨论】:

  • Defmacro 本身是一个宏,而不是一个函数。
  • @Issac Hodes:再检查一下,Clojure 中的 defmacro 是一个宏
  • 艾萨克,你错过了一些东西。在 (def ...) 结束后立即查看。
  • Lisp 中的每个宏都只是一个绑定到 lambda 的符号,并在某处设置了一个小标志,不知何故,eval 会检查,如果设置,则会导致 eval 在宏扩展时调用 lambda 并替换带有返回值的表单。如果您查看 defmacro 宏本身,您会发现它所做的只是重新排列事物,以便您获得一个 var 的 def 以将 fn 作为其值,然后在该 var 上调用 .setMacro,就像核心一样。 clj 正在手动处理 defmacro 本身,因为它还没有用于定义 defmacro 的 defmacro。
  • 对,这确实有道理——我想这确实意味着我应该将它视为一个宏。我想我是从错误的角度来的!谢谢你送我上火车:)

标签: macros clojure lisp scheme primitive


【解决方案1】:

在这里完整地解释它的所有细节需要大量的空间和时间来回答,但大纲真的很简单。每个 LISP 最终在其核心中都有类似于 READ-EVAL-PRINT 循环的东西,也就是说,它需要一个列表,一个元素一个元素,解释它,并改变状态——无论是在内存中还是通过打印结果。

读取部分查看读取的每个元素并对其进行处理:

(cond ((atom elem)(lambda ...))
      ((function-p elem) (lambda ...)))

要解释宏,您只需 (?) 需要实现一个函数,将宏的模板文本存储在某个位置,该函数是该 repl 循环的谓词——这意味着只需定义一个函数——即“哦,这是一个宏!”,然后将该模板文本复制回阅读器,以便对其进行解释。

如果您真的想了解细节,请阅读计算机程序的结构和解释或阅读 Queinnec 的 Lisp in Small PIeces

【讨论】:

  • SCICP 是否深入研究宏?通过快速搜索,我在 PDF 中找不到太多提及它们的内容。
  • 这可能过于简单化了,但你是说如果我编写自己的 eval 函数,只要我的 eval 函数知道宏声明是什么,以及什么是随后对该宏的调用是。
  • 如果您希望宏的语义匹配 CL/Clojure,eval 将首先在表单上调用macroexpand-all,然后将结果传递给不需要知道的原始 eval宏。我以这种方式实现了一个小 lisp,效果很好。
  • @Brian 哦,很好,这很有趣。我正在考虑自己做同样的事情:你用什么语言写的?来源是否发布在某个地方?
  • @Issac Hodes:repl 和原始 eval 是用 Clojure 编写的,花哨的 eval 和宏扩展代码是自己编写的。我刚刚在github.com/qbg/strange 上发布了该项目
【解决方案2】:

尝试在像 McCarthy 的 LISP 机器这样的机器上执行此操作的问题在于,没有办法防止在运行时进行参数评估,也无法在编译时进行更改(宏就是这样做的:他们在编译之前重新排列代码,基本上)。

但这并不能阻止我们在 McCarthy 的机器上在运行时重写我们的代码。诀窍是引用我们传递给“宏”的参数,这样它们就不会被评估。

作为一个例子,让我们看一个我们可能想要拥有的功能; unless。我们的理论函数接受两个参数,pq,并返回 q 除非 p 为真。如果p 为真,则返回 nil。

一些例子(在 Clojure 的语法中,但这并没有改变任何东西):

(unless (= "apples" "oranges") "bacon")
=> "bacon"

(unless (= "pears" "pears") "bacon")
=> nil

所以一开始我们可能想把unless写成一个函数:

(defn unless [p q]
    (cond p nil
          true q))

这似乎工作得很好:

(unless true 6)
=> nil

(unless false 6)
=> 6

使用 McCarthy 的 LISP,它可以正常工作。问题是我们在现代 Lisps 中不仅有无副作用的代码,因此传递给 unless 的所有参数都被评估的事实,无论我们是否想要它们,都是有问题的。事实上,即使在 McCarthy 的 LISP 中,这也可能是一个问题,例如,如果评估其中一个参数需要 ages 来完成,而我们只想很少这样做。但这尤其是一个副作用问题。

所以我们希望我们的unless 评估并返回q 如果p 为假。如果我们将qp 作为参数传递给函数,我们就无法做到这一点。

但是我们可以在将它们传递给我们的函数之前quote它们,从而阻止它们的评估。我们可以使用eval 的强大功能(也已定义,仅使用原语和参考论文后面用原语定义的其他函数)来评估我们需要什么,什么时候需要。

所以我们有了一个新的unless

(defn unless [p q] 
    (cond (eval p) nil 
          true (eval q)))

而且我们使用它有点不同:

(unless (quote false) (quote (println "squid!")))
=> "squid" nil
(unless (quote true) (quote (println "squid!")))
=> nil

然后你就有了可以称为宏的东西。


但这不是defmacro 或其他语言中的等价物。那是因为在 McCarthy 的机器上,没有办法在编译时执行代码。如果您使用eval 函数评估您的代码,它无法知道不评估“宏”函数的参数。阅读和评估之间的区别与现在不同,尽管想法就在那里。在quote 的酷炫和与eval 结合使用的列表操作中,“重写”代码的能力就在那里,但它并没有像现在这样被实习生在语言中(我称之为句法糖,几乎:只要引用你的论点,你就拥有了宏观系统的力量。)

我希望我已经回答了您的问题,而没有尝试自己用这些原语定义一个像样的defmacro。如果您真的想看到这一点,我会向您指出 Clojure 源代码中难以理解的 source for defmacro,或者 Google 更多。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-27
    • 2023-04-01
    • 1970-01-01
    相关资源
    最近更新 更多