Search code examples
clojure

How to bind function variables to current scope of execution in Clojure?


Given:

(defn some-fn
  []
  (let [a 1
        b 2
        c (+ a b)]
    (println c)))

and given that there are multiple such functions, where:

  • a and b have different values;
  • c is always equal to (+ a b)

is there a way to extract c without making it a function, which accepts a and b as arguments. So, I don't want:

(defn c-outside
  [a b]
  (+ a b))


(defn some-fn
  []
  (let [a 1
        b 2
        c (c-outside a b)]
    (println c)))

but ideally, something like:

(defn c-outside
  []
  (+ a b))


(defn some-fn
  []
  (let [a 1
        b 2
        c (c-outside)]
    (println c)))

Is there a way to make c-outside look for the values of a and b in the context, in which it is called? Do I need a macro for that?


Solution

  • there is a way to do it using dynamic bindings:

    user> (def ^:dynamic a 10)
    #'user/a
    
    user> (def ^:dynamic b 20)
    #'user/b
    
    user> (defn c-outside []
            (+ a b))
    
    user> (defn some-fn []
            (binding [a 1
                      b 2]
              (c-outside)))
    #'user/some-fn
    
    user> (some-fn)
    ;;=> 3
    
    user> (c-outside)
    ;;=> 30
    

    the trick is that you can temporarily rebind some dynamic vars for the 'duration' of some scope. This is mostly used in clojure for concurrent programming: the dynamically bound vars keep their values in the threads, spawned from inside the block (as far as i remember)

    Otherwise, i see more potential harm from this feature, than the profit, since it obscures the code, adding some unneeded implicit behaviour.

    Also as far as i know, this is one of the most arguable features in lisps (in common lisp, to be more specific)

    Almost in any case it is better to pass a and b values explicitly to the summing function, since it makes in clean and therefore testable, and helps to reason about it's correctness and performance, and increases readability

    you could also think of using macro for that, like this for example:

    user> (defmacro c-outside-1 []
            `(+ ~'x ~'y))
    #'user/c-outside-1
    
    user> (defn some-fn-1 []
            (let [x 1
                  y 2]
              (c-outside-1)))
    #'user/some-fn-1
    
    user> (some-fn-1)
    ;;=> 3
    

    but that idea is obviously even worse.