2

I have an application in which I have one function that can be called in some different contexts, and I want some of the functions it calls to operate differently in these different contexts. So, as an example, I might have code that functions like:

(defn foo [a context]
  (-> a
      inc
      (#(bar % context))))

(defn bar [a context]
  (cond (= context 1) (* a 2)
        (= context 2) (/ a 2)))

Albeit with a bunch of different functions like bar, that are buried inside other functions that themselves do not care about "context".

Because they're buried inside a bunch of other functions (and because I wrote this code for one context, and now am adding others), modifying all of these functions to pass a flag through to all of the relevant 'bar's is a hassle. I don't really like it as a solution, either. Ideally, I'd want to implicitly use the right version of the bar function in each context.

Protocols might solve the issue. I think it would be a huge hassle to rewrite my function using protocols, though, because (in my actual functions) some of the 'bar' functions both use the context and pass it to other functions that use it. So I think I would have to duplicate some of the code (or have different protocols pass flags).

The solution I came up with was to create a namespace for foo, then a separate namespace for each context. In each context's namespace, then, I define a separate namespace. And I change foo so that it calls the version of the bar function residing in the calling namespace. That is:

(ns main)

(defn foo [a]
  (-> inc
      ('bar (ns-map *ns*))))

(ns context-1
  (use main))

(defn bar [a]
  (* a 2))

(ns context-2
  (use main))

(defn bar [a]
  (/ a 2))

Then when I call foo from the context-1 namespace, it works as intended. Same for the context-2 namespace.

This basically works, except that since I want to then call the context 1 foo and the context 2 foo from a different namespace, I need to write a wrapper for each namespace in which I go into that namespace for the foo function call then switch back to the namespace I started in. So, in the context-1 ns, I write something like:

(defn context-1-foo [a]
  (let [base-ns *ns*]
    (in-ns 'context-1)
    (let [result (foo a)]
      (in-ns (ns-name base-ns))
      result)))

This works, and it didn't require a whole lot of changing, but I think it must be non-idiomatic. It also seems like it could be an invitation to have weird bugs.

What's the idiomatic way to do this? Is there a way to do this that, similarly, requires very little change to the code?

Nick
  • 23
  • 3

1 Answers1

1

Update:

Apologies, after re-reading the question I see that this may not solve you're requirement for the context to be implicit. You would probably have better luck looking into Dynamic Binding and you could create a macro to work like:

(with-context 1
  (bar 4))
;; => 8

(with-context 2
  (bar 4))
;; => 2

Original Answer

The idiomatic way to handle this scenario would be to use Clojure's multimethods. If the method to be called is dependent on what's in the context then multimethods will allow you to dispatch methods that match a particular context and it is future-proof in that you can add more methods by simply specifying the context that it will match.

For your example:

; you could define the context object like:
{:method :bar-1}
; or
{:method :bar-2}

;; Create multimethod that accepts the 2 params 
;; and dispatches on :method in context
(defmulti bar (fn [a context] (:method context)))

;; Method that dispatches when context is {:method :bar-1}
(defmethod bar :bar-1 [a context]
 (* a 2))

;; Method that dispatches when context is {:method :bar-2}
(defmethod bar :bar-2 [a context]
  (/ a 2))

;; Method that dispatches when context is {:method :bar-3}
(defmethod bar :bar-3 [a context]
  ; Some third implementation
  )

Then you would simply call bar with the right context object

(bar 4 {:method :bar-1})
;; => 8

(bar 4 {:method :bar-2})
;; => 2

(bar 4 {:method :bar-3})
;; => nil

Any future implementation of bar can be added with a simple defmethod as seen above.

Community
  • 1
  • 1
jsonmurphy
  • 1,600
  • 1
  • 11
  • 19
  • Thank you for the links, I didn't know about multimethods. Like you said, they aren't implicit, so I'll take a look at dynamic binding too. I'd rather do it implicitly because of how deep the context is used, but if doing it implicitly is too awkward then I'll probably rewrite it idiomatically with multimethods now that I know about them. – Nick Feb 21 '17 at 22:02
  • Let's say I have multiple bar functions, bar-1, bar-2, bar-3, bar-4, etc., and I want each of them to have different behavior in contexts 1 and 2. With dynamic binding, do I write a macro with-context so that when I call (with-context 1 (big-fn-containing-bars input)) it rebinds bar-1, bar-2, bar-3, etc. to the context 1 version of each of those functions? Am I understanding the idea here correctly? – Nick Feb 21 '17 at 22:13
  • I read a bit more and tested some stuff on my own. Thank you, I think dynamic binding is exactly what I need. This is really cool. – Nick Feb 21 '17 at 22:37
  • No prob. Glad i could help. – jsonmurphy Feb 22 '17 at 12:54