Although the below example seems a bit strange, it's because I'm trying to reduce a fairly large problem I've got at present to a minimal example. I'm struggling to work out how to call into multimethods when they're sitting behind a couple of abstraction layers and the defmulti and corresponding defmethods are defined in multiple namespaces. I really feel like I'm missing something obvious here...
Suppose I've got the following scenario:
Using Clojure, the recommended ways of implementing a common interface would be via protocols or multimethods. In this case, as I'm switching based on the value of the supplier, I think the best way to handle the situation I'm describing below is via multimethods (but I could be wrong).
My multimethod definitions would look something like this, which defines a common interface I want to use to talk to every supplier's APIs:
(ns myapp.suppliers.interface)
(defmulti purchase-item :supplier)
(defmulti get-item-price :supplier)
For each supplier, I probably want something like:
(ns myapp.suppliers.supplier1
(:require [myapp.suppliers.interface :as supplier-api]))
(defmethod purchase-item :supplier1 [item quantity] ...)
(defmethod get-item-price :supplier1 [item] ...)
and
(ns myapp.suppliers.supplier2
(:require [myapp.suppliers.interface :as supplier-api]))
(defmethod purchase-item :supplier2 [item quantity] ...)
(defmethod get-item-price :supplier2 [item] ...)
So far, no problem
Now to my code which calls these abstracted methods, which I assume looks something like:
(ns myapp.suppliers.api
(:require [myapp.suppliers.supplier1 :as supplier1]
[myapp.suppliers.supplier2 :as supplier2])
(defn buy-something
[supplier item quantity]
(purchase-item [supplier item quantity])
(defn price-something
[supplier item]
(get-item-price [supplier item])
This is starting to look a bit ... ugly. Every time I implement a new supplier's API, I'll need to change myapp.suppliers.api to :require that new supplier's methods and recompile.
Now I'm working at the next level up, and I want to buy a widget from supplier2.
(ns myapp.core
(:require [myapp.suppliers.api :as supplier])
(def buy-widget-from-supplier2
(buy-something :supplier2 widget 1)
This can't work, because :supplier2 hasn't been defined anywhere in this namespace.
Is there a more elegant way to write this code? In particular, in myapp.core, how can I buy-something from :supplier2?
It's hard to tell if you mixed up some things in the process of simplifying the example, or if they weren't quite right out of the gate. For an example of what I'm referring to, consider purchase-item
, though the issues are similar for get-item-price
:
defmulti
call is a single-argument functiondefmethod
calls each take two argumentsbuy-something
passes a vector to purchase-item
, but looking up the :supplier
keyword in a vector will always return nil
Every time I implement a new supplier's API, I'll need to change myapp.suppliers.api to :require that new supplier's methods and recompile.
myapp.suppliers.interface
namespace myapp.suppliers.api
, the problem can be avoidedThis can't work, because :supplier2 hasn't been defined anywhere in this namespace.
Is there a more elegant way to write this code? In particular, in myapp.core, how can I buy-something from :supplier2?
Without straying too far from your original design, here's a fully-working example of how I interpret what you were trying to achieve:
myapp.suppliers.interface
(ns myapp.suppliers.interface)
(defmulti purchase-item (fn [supplier item quantity] supplier))
myapp.suppliers.supplier1
(ns myapp.suppliers.supplier1
(:require [myapp.suppliers.interface :as supplier-api]))
(defmethod supplier-api/purchase-item :supplier1 [supplier item quantity]
(format "Purchasing %dx %s from %s" quantity (str item) (str supplier)))
myapp.suppliers.supplier2
(ns myapp.suppliers.supplier2
(:require [myapp.suppliers.interface :as supplier-api]))
(defmethod supplier-api/purchase-item :supplier2 [supplier item quantity]
(format "Purchasing %dx %s from %s" quantity (str item) (str supplier)))
myapp.suppliers.api
(ns myapp.suppliers.api
(:require [myapp.suppliers.interface :as interface]))
(defn buy-something [supplier item quantity]
(interface/purchase-item supplier item quantity))
myapp.core
(ns myapp.core
(:require [myapp.suppliers.api :as supplier]))
(def widget {:id 1234 :name "Monchkin"})
(supplier/buy-something :supplier1 widget 15)
;;=> "Purchasing 15x {:id 1234, :name \"Monchkin\"} from :supplier1"
(supplier/buy-something :supplier2 widget 3)
;;=> "Purchasing 3x {:id 1234, :name \"Monchkin\"} from :supplier2"
As you can see, the supplier/buy-something
calls propagate to the appropriate multimethod implementations. Hopefully this helps you get where you were trying to go.