3

Here is the sample code I want to get to work:

(letfn [(CONC [f] f)
        (CONT [f] (str "\newline" f))]
((voodoo "CONC") "hamster"))

Is there some voodo that will make it call the CONC function with hamster as the parameter? That is, is there some way to convert the string "CONC" into a function that is not bound to a namespace but rather to a local binding?

EDIT:

To be clearer, the way this will be called is:

(map #((voodoo (:tag %)) (:value %)) 
    [ 
        {:tag "CONC" :value "hamster"} 
        {:tag "CONT" :value "gerbil"}
    ]
)

4 Answers 4

6

I'd probably solve this by creating a map of functions indexed by strings:

(def voodoo 
  {"CONC" (fn [f] f)
   "CONT" (fn [f] (str "\newline" f))})

Then your desired code should work directly (exploiting the fact that a map is a function that looks up it's argument)

(map #((voodoo (:tag %)) (:value %)) 
    [ 
        {:tag "CONC" :value "hamster"} 
        {:tag "CONT" :value "gerbil"}
    ]
)

Note that the functions here are fully anonymous - you don't need them to be referenced anywhere in the namespace for this to work. In my view this is a good thing, because unless you also need the functions somewhere else then it's best to avoid polluting your top-level namespace too much.

Sign up to request clarification or add additional context in comments.

2 Comments

+1 -- my answer is only concerned with how to implement voodoo, but this is definitely the more sensible solution in most contexts.
This is probably the best answer with the evalive project also being an interesting answer to the question of accessing functions that are not bound in a namespace
4

No. Eval does not have access to the local/lexical environment, ever.

Edit: This is not a very good answer, and not really accurate either. You could write voodoo as a macro, and then it doesn't need runtime access to the lexical environment, just compile-time. However, this means it would only work if you know at compile time that the function you want to call is x, and so it wouldn't be very useful - why not just type x instead of (voodoo "x")?

(defmacro voodoo [fname]
  (symbol fname))

(letfn [(x [y] (inc y))]
  ((voodoo "x") 2))
;; 3

(letfn [(x [y] (inc y))]
  (let [f "x"]
    ((voodoo f) 2)))
;; error

1 Comment

Actually a truly black voodoo which would work is possible, if you're evil enough to use it -- see my answer and Fogus's evalive. :-)
4

Well, it's sort of possible:

(defmacro voodoo [s]
  (let [env (zipmap (map (partial list 'quote) (keys &env))
                    (keys &env))]
    `(if-let [v# (~env (symbol ~s))]
       v#
       (throw (RuntimeException. "no such local")))))

...and now we can do weird stuff like this:

user> (defn example [s]
        (letfn [(foo [x] {:foo x})
                (bar [x] {:bar x})]
          ((voodoo s) :quux)))
#'user/example
user> (example "foo")
{:foo :quux}
user> (example "bar")
{:bar :quux}
user> (example "quux")
; Evaluation aborted.
user> *e
#<RuntimeException java.lang.RuntimeException: no such local>

That "Evaluation aborted" means an exception was thrown.

You could also replace the throw branch of the if in voodoo with (resolve (symbol ~s)) to defer to the globals if no local is found:

(defmacro voodoo [s]
  (let [env (zipmap (map (partial list 'quote) (keys &env))
                    (keys &env))]
    `(if-let [v# (~env (symbol ~s))]
       v#
       (resolve (symbol ~s)))))

...and now this works with definition of example as above (though note that if you are experimenting at the REPL, you will need to recompile example after redefining voodoo):

user> (defn quux [x] {:quux x})
#'user/quux
user> (example "quux")
{:quux :quux}

Now, this is an abuse of Clojure's facilities which one would do well to try to do without. If one cannot, one should probably turn to evalive by Michael Fogus; it's a library which provides an "eval-with-locals" facility in the form of an evil function and a couple of utilities. The functionality seems to be well factored too, e.g. something like the ~(zipmap ...) thing above is encapsulated as a macro and evil there appears to be almost a drop-in replacement for eval (add the env parameter and you're good to go). I haven't read the source properly, but I probably will now, looks like fun. :-)

1 Comment

(Tested with Clojure 1.3-RC0.)
2

Im not really clear what you are asking for so i'll try a couple answers:

if you have a string that is the name of the function you wish to call:

 (def name "+")
 ((find-var (symbol (str *ns* "/" name))) 1 2 3)

this would give voodoo a deffinition like this:

(defn voodoo [name args] (apply (find-var (symbol (str *ns* "/" name))) args))
#'clojure.core/voodoo
clojure.core=> (voodoo "+" [1 2 3])
6

clojure.core=> this assumes your function is in the current namepace ns.

if you want to turn a string into a function you could use this pattern

(let [f (eval (read-string "(fn [] 4)"))] (f))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.