Skip to content

Instantly share code, notes, and snippets.

@briansunter
Created January 24, 2017 08:16
Show Gist options
  • Save briansunter/a1cb736e88b4773c6975f861e8e98cad to your computer and use it in GitHub Desktop.
Save briansunter/a1cb736e88b4773c6975f861e8e98cad to your computer and use it in GitHub Desktop.
clojure spec multimethods as a better protocol

How can we extend functionality in Clojure without modifying the orignal source?

Let's say we want to computer the area of some Shape types like Square and Circle

We have multiple shapes all of which have an area function

We may have some function called sum-areas that takes a list of Shape , calls area on each of them, and sums them up. We don't have access to the original source, but want the ability to add new shapes.

Protocols

(defprotocol Shape
  (area [x]))

(defrecord Rectangle [length width]
Shape
 (area [r] (* length width)))

(defrecord Circle
Shape
  (area [c] (* PI (Math/pow radius 2))))

Spec Multimethods

(s/def :shape/type keyword?)
(s/def :shape/length pos-int?)
(s/def :shape/width pos-int?)
(s/def :shape/radius pos-int?)

(defmulti shape-type :shape/type)
(s/def :shape/shape (s/multi-spec shape-type :shape/type))

(defmethod shape-type :shape/rect [_]
  (s/keys :req [:shape/length :shape/width]))

(defmethod shape-type :shape/circle [_]
  (s/keys :req [:shape/radius]))

(defmulti area :shape/shape)

(defmethod area :shape/rect
  [{:keys [length width]}]
  (* length width))

(def PI 3.14340)

(defmethod area :shape/circle
  [{:keys [radius]}]
  (* PI (Math/pow radius 2)))

Now we want to add 3d objects that have a surface (area) but also have volume

Protocols

(defprotocol Solid
(volume [s]))

(defrecord Cube [length width height]
Shape
(area [c] (+ (* 2 length width) (* 2 length height ) (* 2 width height)))
Solid
(volume [c] (* length width height)))

Spec Multimethods

(s/def :solid/height pos-int?)

(defmethod shape-type :solid/cube [_]
  (s/keys :req [:shape/length :shape/width :solid/height]))

(defmethod area :shape/cube
  [{:keys [length width height]}]
    (+ (* 2 length width) (* 2 length height ) (* 2 width height)))

(defmulti solid-type :solid/type)

(s/def :solid/solid (s/multi-spec shape-type :solid/type))

(defmethod area :solid/cube
  [{:keys [length width height]}]
    (+ (* 2 length width) (* 2 length height) (* 2 width height)))

(defmulti volume :solid/type)

(defmethod volume :solid/cube
  [{:keys [length width height]}]
  (* length width height))
@MageMasher
Copy link

For the circle defrecord you presumably you meant:

(defrecord Circle [radius]
  Shape
  (area [c] (* PI (Math/pow radius 2))))

Everything else compiles!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment