Skip to end of metadata
Go to start of metadata

This is the page for discussing the design of native macros in Clojurescript.

 

Clojurescript already has a non-native macro system in the form of Clojure macros.  However, this system has some downsides, the most prominent being that they require the programmer to define them in a different file and namespace, and that they must be written in what is a slightly different dialect of the language.  Also, if the Clojurescript compiler is ever to be a Clojure-in-Clojure compiler, then it must have its own macro system.

 

Design

  • Macros require that the compiler is able to evaluate functions written in the language.
    • Since the syntax for defining macros is essentially the same as for defining functions, the defmacro parser should be able to call the defn parser.
  • The compiler must call these functions with unevaluated code as input.
  • Macroexpansion occurs in the compiler in the compiler/macroexpand-1 function, called from compiler/analyze.
  • In order to run macros properly, all definitions must be evaluated (e.g. def, defn, deftype, extend-type).
  • The evaluator should be dependent on the backend being targetted.  Initially we should target the JavaScript backend, as it is currently the most mature.
    • Any REPL implementation that implements the IJavaScriptEnv protocol should theoretically work as an evaluator.

Implementation

 

  • There is an implementation that follows the above design in the repository here.
  • It is a fairly small and self-contained change to the compiler.
    • It does not remove Clojure macros; it only adds Clojurescript macros.
  • It can be run optionally.
  • It compiles the existing test suite without error as well as newly-added tests that introduce macros.

Details

  • The implementation discussed above does not pass the &env and &form variables to macros that the Clojure compiler does.  It should not be a very difficult change to add them, but it would be nice to have a discussion about whether they are truly useful features to have.  
    • In core.match for example, it seems that &env is the only way that it could be determined if a symbol is an existing local or a wildcard.  This is a good thing, but is there a better way of doing it than having magic variables?
  • Clojurescript uses the Clojure reader.  In the Clojure reader, backquoted symbols are resolved to namespaces.  Since the Clojure reader doesn't know what is defined in Clojurescript namespaces, it has no way of doing this correctly for Clojurescript macros.  Thus it can incorrectly resolve backquoted symbols.
    • This can be worked around by manually resolving symbols, but that is probably not the optimal situation.
Labels:
  1. Apr 24, 2012

    > The evaluator should be dependent on the backend being targetted.

    What if you're targeting the browser? You can't easily run the browser server-side, so you have to choose between V8 and Rhino.

    I'd suggest V8 because...

    • V8 is the runtime used by Google Chrome, so it's already most compatible with a true browser environment.
    • Rhino can interop with Java, so it's easier to accidentally depend on a Java library.
    • Node.js is now far more popular than Rhino and probably a preferable backend for server-side ClojureScript.

    Should this be configurable with a dynamic variable as well?

    1. Apr 24, 2012

      My current implementation uses Rhino because an implementation of IJavaScriptEnv already existed for it.  I agree that V8 is a better target long-term, but it is a somewhat separate project.  Another advantange that it has that you didn't mention is that Rhino's performance is pretty terrible (especially in terms of startup time).


      Making the repl environment be a dynamic variable does seem like a good idea.  Perhaps there could be a variable denoting which backend is being used and the macro system could determine which repl environment to use based on that.

  2. Apr 25, 2012

    Under motivation, shouldn't there also be the point that you can use clojurescript/js information to influence the macro? One example of this feature would be for reflection related macros.

    1. Apr 25, 2012

      The plan is to have a general-purpose macro system, so I think that the application that you mention should be covered.  I might be misunderstanding your suggestion, though.  Do you have an example of how it would be used that would highlight this advantage?

      1. Apr 25, 2012

        Maybe a clearer example is let's say you have a large complicated function that is written in clojurescript or js, when using clojure macros, you can't call these functions to operate on the symbols. So in performance critical code, you can use this trick to perform an expensive lookup at compile time. While this may not seem a big deal for common web applications, but in a situation like node.js, I could see this being more applicable. I feel like this is a large component of the motivation.