Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Executing a dynamically bound function in Clojure

I'd like to pre-store a bunch of function calls in a data structure and later evaluate/execute them from within another function.

This works as planned for functions defined at namespace level with defn (even though the function definition comes after my creation of the data structure) but will not work with functions defined by let [name (fn or letfn inside the function.

Here's my small self-contained example:

(def todoA '(funcA))
(def todoB '(funcB))
(def todoC '(funcC))
(def todoD '(funcD)) ; unused

(defn funcA [] (println "hello funcA!"))

(declare funcB funcC)

(defn runit []
    (let [funcB (fn [] (println "hello funcB"))]
    (letfn [(funcC [] (println "hello funcC!"))]
        (funcA)       ; OK
        (eval todoA)  ; OK
        (funcB)       ; OK
        (eval todoB)  ; "Unable to resolve symbol: funcB in this context" at line 2
        (funcC)       ; OK
        (eval todoC)  ; "Unable to resolve symbol: funcC in this context" at line 3
)))

In case you're wondering about my test setup, to see the result of those 6 statements I comment/uncomment specific of the OK/failing lines and then call (runit) from the REPL.

Is there a simple fix I could undertake to get eval'd quoted calls to functions to work for functions defined inside another function?


Update:

This (based on danlei's suggestion) does work. Let's see if I can get this method working in "real life!"

(def todoB '(funcB))
(declare funcB)

(defn runit []
  (binding [funcB (fn [] (println "hello funcB"))]
    (funcB)
    (eval todoB)  ; "Unable to resolve symbol: funcB in this context" at line 1!
))

Update:

This code is going into my solution for a Constraint Satisfaction Problem - I want to find out who owns the zebra! I'm fairly new to Clojure and especially functional programming, and this has made the exercise quite challenging. I'm falling into a lot of pits but I'm OK with that as it's part of the learning experience.

I used to specify the constraints as a bunch of simple vectors, like this:

[:con-eq :spain :dog]
[:abs-pos :norway 1]
[:con-eq :kools :yellow]
[:next-to :chesterfields :fox]

where the first of each vector would specify the kind of constraint. But that led me to an awkward implementation of a dispatch mechanism for those rules, so I decided to encode them as (quoted) function calls instead:

'(coloc :japan :parliament) ; 10
'(coloc :coffee :green) ; 12
'(next-to :chesterfield :fox) ; 5

so I can dispatch the constraining rule with a simple eval. This seems a lot more elegant and "lisp-y." However, each of these functions needs to access my domain data (named vars), and this data keeps changing as the program runs. I didn't want to blemish my rules by introducing an extra argument, so I wanted vars to be available to the eval'd functions via dynamic scoping.

I've now learned that dynamic scoping can be done using binding, but it also needs a declare.

like image 203
Carl Smotricz Avatar asked Apr 04 '10 17:04

Carl Smotricz


2 Answers

Do you mean something like this?

(def foo '(bar))
(declare bar)

(binding [bar (fn [] (println "hello bar"))]
  (eval foo))

If yes, your problem reduces to this:

(let [foo 1]
  (eval 'foo))

This won't work, because eval does not evaluate in the lexical environment. You can get around that using vars:

(declare foo)

(binding [foo 1]
  (eval 'foo))

As far as that is concerned, Clojure seems to have similar semantics to CL, cf. the CLHS:

Evaluates form in the current dynamic environment and the null lexical environment.

like image 74
danlei Avatar answered Nov 15 '22 09:11

danlei


I think you're solving the wrong problem. In functional languages, functions are values and can be assigned to anything that can store any other value, e.g. a map. You shouldn't be trying to manipulate namespaces or evaling anything - this isn't perl.

Try something like this, and use assoc to change the map locally:

user=> (def fnmap {:funcA (fn [x] (inc x)), :funcB (fn [x] (* x 2))})
#'user/fnmap
user=> ((:funcA fnmap) 10)
11
user=> ((:funcB fnmap) 10)
20
like image 41
Chris Avatar answered Nov 15 '22 08:11

Chris