1
votes

I am trying to write a function eval-with-bindmap which takes two parameters, an expression to evaluate and a map of symbols to values which will act as bindings in the evaluation. I cannot use let since the bindings may be in any order in the map (it should work with both array-maps and hash-maps) and it may also contain bindings of functions which are mutually recursive (as in letfn). For example:

(eval-with-bindmap '(+ a b) '{a (+ 1 b) b 2}) => 5

Any tips on how to approach this would be awesome, thank-you.

2
Why do you want to allow the bindings to be in any order? What if there is a circular dependency? If you really want to allow that, you'd basically first need to implement an algorithm to ensure a topological sorting. - Andreas Profous
Since this function needs to work with hash-maps the bindings may be in any order. We can assume that there will be no direct circular dependencies in the binding map however there may be symbols bound to functions which call other functions in the binding (like you can in letfn). - OParry
What about security? where does the expression come from? - coredump

2 Answers

1
votes

Some simple recursive processing could work (unless your form expansion is really deep, otherwise you should think about tail recursive version)

(defn eval-with-bindings [form bind]
  (cond (seq? form) (apply (resolve (first form))
                           (map #(eval-with-bindings % bind) (rest form)))
        (and (symbol? form) (contains? bind form)) (eval-with-bindings (bind form) bind)
        :else form))

user> (eval-with-bindings '(+ a b) '{a (+ 1 b) b 2})
5

in case of infinite circular deps it would just fall with stackoverflow:

user> (eval-with-bindings '(+ a b) '{a (+ 1 a) b 2})
StackOverflowError   clojure.lang.AFn.applyToHelper (AFn.java:148)
1
votes

I'd say, the better approach would be still to use eval inside let, but process your binding maps into let vectors in a special way that cares about a proper order.

For example, say you've got a map {a (+ 1 b) b 2}. For each key/expression pair, you check if an expression includes any alpha-numeric symbols. The b would be our candidate. In that case, you move b 2 before the a (+ 1 b) and so on.

Yes, the proper algorithm might be a bit complicated, but then you won't have any troubles with eval in further.