Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IllegalStateException: Attempting to call unbound fn in macro

Tags:

macros

clojure

I'm trying to write a macro that calls some functions. The functions should only be used by the macro, so I put them inside of a letfn wrapping the macro. Pseudocode:

(letfn [(fn-a [] ...)
        (fn-b [] ...)
        (fn-c [] (fn-b))]
  (defmacro my-macro [stuff]
   `(let [whatever# (fn-a)]
      (fn-c))))

The calls to fn-a and fn-c work, but when fn-c tries to call fn-b I get IllegalStateException: Attempting to call unbound fn: #'name.space/fn-b. Why is that?

If I put fn-b and fn-c in their own defns, everything works. But I don't want to do that because it isn't clean.

Edit: Just to test, I tried putting the function bindings in the inner let but ran into the same exception.

like image 531
Ken Avatar asked Aug 12 '14 06:08

Ken


Video Answer


2 Answers

I don't think this can work at all this way. The call to fn-c for example gets expanded to your.namespace/fn-c, so your code seems to call other functions that just happen to have the same names. But you dont have a your.namespace/fn-b, which raises the exception.

To refer to a unqualified symbol you need to quote and unquote it: ~'fn-a But this won't work either because the local functions aren't defined at the point of expansion, you can only use them for the macro itself.

You either have to define the functions in a namespace and qualify them in the macro or include them in the macroexpansion, which would define them again at each use.

like image 83
hardcoder Avatar answered Oct 09 '22 11:10

hardcoder


I'm not sure if this is exactly what you're after but if I do:

(letfn [(fn-a [] (println 1))
        (fn-b [] (println 2))
        (fn-c [] (fn-b))]
  (defmacro my-macro [stuff]  
    `(let [whatever# ~(fn-b)]
       ~(fn-c))))

then it works - it just needs the tilde to unquote the function calls.

Both of the following work:

(defn fn-x [] (println 1))

(defn fn-y [] (fn-x))

(defmacro my-macro2 [stuff]  
        `(let [whatever# (fn-x)]
           (fn-y)))

AND

(defn fn-x [] (println 1))

(defn fn-y [] (fn-x))

(defmacro my-macro2 [stuff]  
        `(let [whatever# ~(fn-x)]
           ~(fn-y)))

In the first case, the functions are being evaluated and their results incorporated into the macro at compile time whereas in the second they are being evaluated at runtime. With the letfn (which is a macro itself) the results are not available at compile time (presumably because they are being compiled after your macro is compiled) so the functions can only be used at runtime.

like image 38
optevo Avatar answered Oct 09 '22 10:10

optevo