b3rt0
b3rt0

Reputation: 779

Clojure multimethods/protocols implementations

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:

  1. Is there a way to implement this function with protocols or is this a multimethod only problem?

  2. Do protocols only work with type based recognition? Like stated here https://stackoverflow.com/a/8074581/1537744 ?

Upvotes: 2

Views: 211

Answers (1)

Kyle Grierson
Kyle Grierson

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

Related Questions