Reputation: 779
I am new to clojure and I am enjoying it a lot. Now, I am trying to step up and use more features of the language: multimethods and protocols
I have seen many blog posts, documents and questions out there touching on this topic but I am still not sure that I get it.
Assume I have a function that changes behavior based on the type of the input argument. I can implement the function either through multimethod or protocol:
;; Multi
(defmulti foo class)
(defmethod foo java.lang.Double [x]
"A double (via multimethod)")
(defmethod do-a-thing java.lang.Long [x]
"A long (via multimethod)")
;; Protocol
(defprotocol Bar
(bar [x] "..."))
(extend-protocol Bar
java.lang.Double
(bar [x] "A double (via protocol)")
java.lang.Long
(bar [x] "A long (via protocol)"))
Both work and it seems that the protocol approach is preferable for speed purposes.
However, I would like to implement a mathematical function (such as area) which depends on an input parameter. One approach would be to use case but I don't find it clean.
So I tried the multimethod version. It works like a charm:
(defmulti area (fn [shape & _]
shape))
(defmethod area :square
[_ x]
(* x x))
(defmethod area :circle
[_ r]
(* r r Math/PI))
(defmethod area :triangle
[_ b h]
(* 1/2 b h))
But the following protocol implementation doesn't work:
(defprotocol Surface
(surface [x] 0.0))
(extend-protocol Surface
:square
(surface [x] (* x x))
:circle
(surface [r] (* r R Math/PI))
:triangle
(surface [b h] (* 1/2 b h)))
I get the following error:
Execution error (ClassCastException) at user/eval2081 (REPL:1).
class clojure.lang.Keyword cannot be cast to class java.lang.Class (clojure.lang.Keyword is in unnamed module of loader 'bootstrap'; java.lang.Class is in module java.base of loader 'bootstrap')
My questions are:
Is there a way to implement this function with protocols or is this a multimethod only problem?
Do protocols only work with type based recognition? Like stated here https://stackoverflow.com/a/8074581/1537744 ?
Upvotes: 2
Views: 211
Reputation: 346
Here is a great resource on protocols
Protocol functions dispatch on the type of their first argument.
(defprotocol Area
(area [this] "Get area of shape"))
(defrecord Square [x]
Area
(area [this] (* (:x this) (:x this))))
(defrecord Circle [r]
Area
(area [this] (* (:r this) (:r this) Math/PI)))
(defrecord Triangle [b h]
Area
(area [this] (* 1/2 (:b this) (:h this))))
(map area [(Square. 10) (Circle. 10) (Triangle. 10 15)])
Upvotes: 4