I'm tangled in a macro mess.
Background: The idea is that I define a 'situation object' (a hash map) that contains a logical condition, some text, and a variable number of 'options'. Each option has also a condition, some text and some effects. The conditions and effects are turned into anonymous functions, the text is just preserved.
So I want to define a macro that transforms the expression into a couple nested hashmaps, including some generated lambda functions for evaluating in runtime. I've copied some ideas from here for making the make-fn
macro to use map inside other macros, but maybe that's the wrong way to go...
The code:
; Two basic (silly maybe? let me know if I'm reinventing the wheel)
; macros for turning conditions and effects into functions.
(defmacro lambdify [& body] `(fn [] (do ~@body)))
(defmacro condify [body] `(fn [] ~body))
(defmacro make-fn [m]
`(fn [& args#]
(eval `(~'~m ~@args#))))
; option and option-in-list do the same but one takes a single parameter
; as a list. I'm trying out different things.
(defmacro option
[cnd text & effs]
`(hash-map :cond (condify ~cnd)
:text ~text
:effects (lambdify ~@effs)))
(defmacro option-in-list
[expr]
`(hash-map :cond (condify ~(first expr))
:text ~(second expr)
:effects (lambdify ~@(next (next expr)))))
(defmacro options
[& opts]
`(map (make-fn option-in-list) ~opts))
(defmacro situation
[conds title text & opt-list]
`(hash-map :cond (condify ~conds)
:title ~title
:text ~text :opts (options ~@opt-list)))
I want to start with the situation, so if I define this:
(situation (< 1 5) "title" "desc"
((> 1 2) "option1" (println "option 1 chosen"))
((< 1 5) "option2" (println "option 2 chosen"))))
I expect to have a hash map like
{ :cond (.. the fn for (< 1 5)),
:title "title" ,
:text "desc",
:opts [{:cond ( the fn for (> 1 2))
:text "option1"
:effects: ( the fn for println... )}
{ .. the second option also as a hash}}
I know that option
and option-in-list
work:
(option (= 1 2) "option2" (println "option 2 chosen"))
{:text "option2", :effects #, :cond #}
(option-in-list ((= 1 2) "option2" (println "option 2 chosen")))
{:text "option2", :effects #, :cond #}
But when I evaluate either options
or situation
, I get:
(options ((< 1 5) "option1" (println "option 1 chosen"))
((= 1 2) "option2" (println "option 2 chosen")))
java.lang.ClassCastException: java.lang.Boolean cannot be cast to clojure.lang.IFn
As I understand, that means that somewhere it's trying to evaluate a list that has a boolean as it's first member, and therefore it's attempting to cast it to a function, right?
But I cannot find where (or how) that is happening.
Any guidance will be appreciated, I don't have much experience with Clojure and I'm not even sure if I'm going in a completely wrong direction.
EDIT
I've managed to make something similar that works, but I would like to know why..
If I do this:
(def sit
(situation (< 1 5) "title" "desc"
(option (> 10 2) "option1" (println "option 1 chosen"))
(option (< 1 5) "option2" (println "option 2 chosen"))))
(declaring explicitly option in the situation declaration) then it works. So There is something wrong in my (understand of) invocation of macros from within macros. Is there a way to make it work without using the option
macro directly (i.e. making it called from within the situation
macro)?
situation
needs to be a macro, but the things it calls, directly or indirectly, had better be functions. – Thumbnailsituation
isn't actually calling any of those functions; it is expanding to a call to them. You could rewrite things so that these helper macros are functions, but there's no need to. – amalloycondify
andlambdify
could be functions as they need exactly what macros do: to generate code without evaluating it. Is there a way to make so with functions, and will those functions be available while processing the macros? – Sebastian