3
votes

I have a CLJC file that produces the following desired output in Clojure:

(ns myproj.macros-ns)

(defmacro inner-macro [s]
  `['(my-ns/my-fn) :from ['~s :all]])

(defmacro outer-macro [y xs]
  `(into ['~y '~'<-] (eval '~xs)))

(defmacro macro-context [other-macros]
  (let [symbols (eval other-macros)
        _ (println "Expanded in macro context" symbols)]
    {:result `(list '~symbols '~'+ '~'further-rearrangement)}))


(macro-context (outer-macro ?sym-a (inner-macro ?sym-b)))

Expanded in macro context [?sym-a <- (my-ns/my-fn) :from [?sym-b :all]]

=> {:result ([?sym-a <- (my-ns/my-fn) :from [?sym-b :all]] + further-rearrangement)}

My question is: How can I get the same result in Clojurescript?

My CLJS file looks like this:

(ns myproj.app-ns
   (:require-macros [myproj.macros-ns :refer [outer-macro
                                              inner-macro
                                              macro-context]]))

(enable-console-print!)

(macro-context (outer-macro ?sym-a (inner-macro ?sym-b)))

Error I'm getting is:

clojure.lang.ExceptionInfo: java.lang.RuntimeException: Unable to 
resolve symbol: outer-macro in this context, compiling:
(/private/var/folders/2g/sfp74ftj6_q1vw51ytjbgvph0000gn/T/form-
init4244939051951953637.clj:13:3) at line 12 
test/macros/cljs/myproj/app_ns.cljs

What am I ultimately trying to do and why?

I'm writing a framework that wraps https://github.com/cerner/clara-rules. Clara has its own macro, defrule, that uses the following DSL syntax

(defrule my-rule
 [?fact <- (my-ns/my-fn) :from [:all (= ?e (:e this))]
 ...

I have a macro that expands the following to the previous:

(macro-context my-rule
 [?fact <- (my-ns/my-fn) :from [?e :all]]
...

The macro that does this is basically the macro-context in the more general example above. When I am only parsing syntax like this, I don't call eval or macroexpand. I'm able to treat everything as a symbol, rewrite it to Clara's DSL, then pass it to defrule.

Here's where I think that breaks down:

(macro-context
 [(outer-macro ?fact (inner-macro ?e))]
 ...

Inside the macro-context macro, outer-macro and inner-macro are unevaluated and at that point I need their expansion. By calling eval, I can get that in Clojure, but for some reason when compiling Clojurescript I receive "unable to resolve symbol outer-macro in this context.

2
This is a very strange way to solve a problem, and it's a little unclear what actual problem is motivating this solution. You've sorta provided "here is a thing I tried to do, and it didn't work", which leaves me thinking "yes, that definitely will not work, but I don't know what else to suggest because I don't know why you wanted to do this". Your solution looks wrong for clj-jvm too, and you are just getting away with it by coincidence. But again, I can't say even that for sure without knowing what the point is of generating these strange forms at macro time.amalloy
Thanks for your response! I will provide more context and general information about the problem I am trying to solve.ahhhhhhhhhhhhhhhhhhhhhhhhhhhhh
You should use macroexpand instead of eval.ClojureMostly
@ClojureMostly No, that is not a correct solution. If f and g are both macros, then (macroexpand '(f (g x))) is different from (eval '(f (g x))) (or the usual thing, simply writing (f (g x))): the first of those does not expand g, while the last two do.amalloy

2 Answers

4
votes

When the form (outer-macro ?sym-a (inner-macro ?sym-b)) is passed into macro-context, the (ClojureScript) :refer for outer-macro and inner-macro don't affect the Clojure macroexpansion. In particular, the eval employed in macro-context will not be able to resolve those symbols.

But if you qualify those symbols, say, with

(macro-context (myproj.macros-ns/outer-macro ?sym-a (myproj.macros-ns/inner-macro ?sym-b)))

then things will work.

Update:

It is possible to effect the needed refers in Clojure if you add a refer in your macro definition like this:

(defmacro macro-context [other-macros]
  (refer 'myproj.macros-ns :only '[inner-macro outer-macro])
  (let [symbols (eval other-macros)
        _ (println "Expanded in macro context" symbols)]
    {:result `(list '~symbols '~'+ '~'further-rearrangement)}))

With this, inner-macro and outer-macro will be referred in the Clojure *ns* mirroring the ClojureScript ns you're expanding from. Then referring macro-context in ClojureScript is sufficient and symbols resolve.

0
votes

What you are trying to do looks as though it should be quite simple and easy, because we are used to thinking in terms of functions. Functions are nicely composable, and you can easily split up a large function into several small functions and then compose the results of each into one larger result. Macros don't really have this property: you sorta have to craft one giant ball of mud to do everything at once, and breaking things up into smaller macros doesn't generally work. So unhappily, what you want to do is in fact rather hard!

The most straightforward approach would be to give in to the ball of mud, so to speak, since Clara has dictated that you have to do all your interesting logic in macro-land. You could declare that your topmost macro must know ahead of time all the ways in which it is possible for the problem to be broken up, accepting arguments dictating what transformations should be made, and then making those transformations itself, rather than delegating that task to another macro (since as we said, you can't decompose a macro). Anytime you need to introduce a new kind of flexibility, edit that "master" macro to add another option to it (probably a good idea to take them as an "options" map rather than positional parameters). Kinda sucks, but that's life sometimes.

A more maintainable approach with a higher initial cost would be to try to get yourself back into the land of functions: write one "master macro" as above, but decompose it into functions rather than into macros. A macros is, after all, just a function that takes in source code and returns source code, with the key distinction that it is expanded in-place. You can write a function that behaves the same but isn't expanded in place, and have macro implementations call it when expanding themselves.

Sadly I don't really have the time to write out example snippets of what I mean for either solution, partly because it is rather hard to keep straight in my head how all the example macros you have provided are supposed to work together. But hopefully this pile of prose proves somewhat useful nonetheless.