caeus
caeus

Reputation: 3726

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

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?

Upvotes: 0

Views: 138

Answers (2)

James Elliott
James Elliott

Reputation: 1017

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.

Upvotes: 2

erdos
erdos

Reputation: 3538

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.

Upvotes: 4

Related Questions