Clojure:Clojure 如何知道一个引用实际上是宏?

huangapple go评论91阅读模式
英文:

Clojure: How does Clojure know when a reference is actually a macro?

问题

绑定到变量来自let表达式、导入以及可能还有其他来源(我是Clojure的初学者)。所以要知道某些内容是引用宏并不是一件简单的事情。

我仍然不知道宏何时展开,但考虑到Clojure的动态性质,我会说它们在解释期间展开(?),但那么,Clojure如何知道传递的参数不应该被评估,而应该作为AST传递?

是否有详细解释其工作原理的文档?

英文:

Binding to variables come from let expressions, imports and probably there are other sources (I'm a beginner in Clojure). So it's not trivial to know something references a macro.

I still don't know when are macros expanded, but given the dynamic nature of Clojure I'd say they're expanded during interpretation(?), but then, how does Clojure know that the passed parameters shouldn't be evaluated, but instead passed as a AST?

Is there documentation that thoroughly explains how it works?

答案1

得分: 3

宏只是解析为函数并且其元数据中的:macro键的值设置为true的变量。您可以在defmacro定义中看到它是如何设置的。

因此,您可以直接从元数据中获取这个数据:

user=> (:macro (meta #'let))
true

或者,您也可以使用一个getter来检查一个变量是否是宏:

user=> (.isMacro #'let)
true
user=> (.isMacro #'inc)
false

[...] 但是,Clojure 如何知道传递的参数不应该被评估,而是作为 AST 传递?

宏扩展期间,Clojure 编译器调用了.isMacro在变量上

英文:

Macros are just vars that resolve to a function and have the value for :macro key in their metadata set to true. You can see how it is set in the definition of defmacro.

Therefore, you can get this data directly from the metadata:

user=> (:macro (meta #'let))
true

Alternatively, you can just use a getter to check whether a var is a macro:

user=> (.isMacro #'let)
true
user=> (.isMacro #'inc)
false

> [...] but then, how does Clojure know that the passed parameters shouldn't be evaluated, but instead passed as a AST?

During macroexpansion, the Clojure Compiler calls the .isMacro on the var.

答案2

得分: 1

根据Clojure文档的描述,宏允许编译器通过用户代码进行扩展。这在你可能听说过的类似Lisp的语言中,通常在所谓的读取-求值-打印循环中发生。第一阶段读取一个字符串并将其转换为Clojure数据结构,表示它的内容(你称之为AST,但由于Lisp方言是同构的,源代码与解析后的数据结构之间存在比AST中通常找到的更密切的关系)。

在第二阶段,即评估阶段,编译器将解析后的结构转换为实际可加载和运行的Java字节码时,宏开始发挥作用。当编译器找到引用宏的变量时,它会调用该宏函数,使用在该表达式的主体内找到的解析代码,并用宏返回的任何数据结构替换宏调用。然后,编译器继续编译结果,这可能涉及更多的宏展开,但最终归结为字节码。通过这种方式,宏可以根本性地改变和扩展语言,这使它们强大,但也使它们难以编写,因为你需要同时考虑完全分离的抽象层次,并将被编译的结构与后来在运行时存在的结果关联起来。

这也意味着,与普通的Clojure函数不同,宏不是头等公民;你不能将它们传递给其他函数以形成高阶构造,因为一旦编译完成,宏就不再存在,它已被替换为它返回的任何内容。这就是为什么当你尝试将宏作为另一个函数的参数传递时,会看到错误消息“无法取宏的值”。

正如另一个答案已经解释的那样,编译器可以通过附加到包含宏的变量的元数据来判断它是否在编译宏。

如果你真的想深入理解宏在Lisp中发挥的基本作用,《Let Over Lambda》是一本有趣的读物。

英文:

As the Clojure documentation describes, macros “allow the compiler to be extended by user code.” This is quietly telling us when it happens during the read-eval-print-loop you’ve probably heard discussed for Lisp-like languages. The first stage reads a string and converts it into the Clojure data structures that it represents (what you called an AST, but since Lisp dialects are homoiconic, there is a closer relationship between the source and the parsed data structures than one usually finds in an AST).

It is during the second stage, evaluation, when the compiler is turning the parsed structures into Java byte code that can actually be loaded and run, that macros come into play. When the compiler finds a var that references a macro, it calls that macro function with the parsed code that is found inside the body of that expression, and then replaces the macro invocation with whatever data structures the macro returns. It then goes on to compile the result, which may involve more macro expansions, but eventually boils down to byte code. In this way, macros can fundamentally change and extend the language, which makes them powerful, but it also makes them challenging to write because you need to keep completely separate hierarchies of abstraction in mind simultaneously, and relate the structures being compiled to the results that exist later at runtime.

This also means that, unlike normal Clojure functions, macros are not first-class; you can’t pass them to other functions to form higher-order constructs, because by the time compilation is finished, the macro no longer exists, it has been replaced by whatever it returned. This is why you will see the error “Cannot take value of a macro” when you try to pass one as an argument to another function.

And as another answer has explained, the compiler can tell when it is compiling a macro because of metadata attached to the var that holds the macro.

If you really want to go deep in understanding the fundamental role macros play in Lisps, Let Over Lambda is an interesting read.

huangapple
  • 本文由 发表于 2023年6月5日 00:26:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/76401372.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定