Meta-Clojure provides staged compilation for Clojure. It includes a form of syntax quoting that is aware of both local environments and special-forms. Among other things, this makes many macros easier to write. Perhaps more importantly, it simplifies control over when code gets evaluated or compiled.
(require '[metaclj.core :refer [defmeta defbn syntax] :as meta])
The defmeta
form is analogous to defmacro
, but is expected to return
Syntax objects (forms plus their environments) instead of plain forms.
(defmeta my-if [test then else]
(syntax (if test then else)))
Note that you don't need to unquote any of the parameters to if
, since the
syntax
form is aware of the meta-macro's environment.
Since it's common for macros to have a body that always templates code with
a syntax-quoter, the convenience macro defbn
provides a way to create
"call-by-name" macros:
(defbn my-if [test then else]
(if test then else))
Both versions of my-if
have correct "lazy" behavior: they will only evaluate
one arm of the conditional.
The meta/do
macro will perform meta-quoting on zero or more forms, then
evaluate each of them:
(meta/do 1 2 3)
;;=> 3
Combined with unquoting, this enables you to perform arbitrary computation at compile time:
(let [x 2 y 4]
(meta/do ~(+ x y)))
;;=> 6
Unquoting is syntax aware and provides automatic splicing:
(let [args (syntax 2 4)]
(meta/do ~(+ args)))
;;=> 6
You can use function expressions to defer computation. Note that the unquoted expression will still be evaluated at compile time:
(let [x 2 y 4]
(meta/do (fn [] ~(+ x y))))
;=> #<Fn@32b2ad39 user/eval15784$fn__15788>
You can prove this to yourself by using meta/translate
, which is a cousin
of macroexpand-all
:
(let [x 2 y 4]
(meta/translate (fn [] ~(+ x y))))
=> ((fn* ([] 6)))
Note that the returned value is wrapped in a seq, since Meta-Clojure uniformly supports multiple expressions with implicit splicing:
(let [x (syntax 2 3)]
(meta/translate 1 x 4))
;=> (1 2 3 4)
- The comments at the bottom of core.clj and the code in core_test.clj form my testbed.
- Many known bugs and incomplete behavior.
- Some special forms not yet supported:
case
,deftype
, andreify
. - No progress yet on Exotypes
- Use of
clojure.core/eval
is unavoidable at the top level, but it could be compiled away for more interior forms. - Maybe someday I'll revive EClj and build its compiler on Meta-Clojure.
Copyright Β© 2016 Brandon Bloom
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.