Records and Protocols
Define a record:
(defrecord Musician [name instrument band])Use the automatically generated factory functions to create a new record instance:
(def jimmy (->Musician "Jim Matheos" "Guitar" "Fates Warning"))
(def bobby (map->Musician {:name "Bobby Jarzombek"
:instrument "Drums"
:band "Fates Warning"}))Records can be used like maps:
(:instrument jimmy) ; "Guitar"
(count bobby) ; 3Extend a record with additional fields:
(assoc jimmy :first-album "Night on Bröcken")
;; {:name "Jim Matheos",
;; :instrument "Guitar",
;; :band "Fates Warning",
;; :first-album "Night on Bröcken"}
(:instrument jimmy) ; "Guitar"Access to pre-defined record fields is faster than access to additional fields added later.
Define a protocol:
(defprotocol Personalized
(name [this])
(describe [this]))Implement a protocol for different record types:
(defrecord Musician [name instrument band]
Personalized
(name [this]
(:name this))
(describe [this]
(str (:name this) " plays " (:instrument this) " for " (:band this))))
(defrecord Employee [name position company]
Personalized
(name [this]
(:name this))
(describe [this]
(str (:name this) " works as a " (:position this) " for " (:company this))))Use the polymorphic functions on both record types:
(def people [(->Musician "Ian Hill" "Bass" "Judas Priest")
(->Employee "Ashok" "Intern" "ACME Corp.")])
(map describe people)
;; ("Ian Hill plays Bass for Judas Priest"
;; "Ashok works as a Intern for ACME Corp.")Implement a protocol for an existing type:
(defprotocol Greetable
(greet [this greeting]))
(extend-protocol Greetable
Musician
(greet [this greeting]
(str greeting ", " (name this) "!"))
Employee
(greet [this greeting]
(str greeting ", underling " (name this) ".")))
(map #(greet % "Hi") people) ; ("Hi, Ian Hill!" "Hi, underling Ashok.")Protocols can be implemented for any existing types, including
String, Boolean, and the like.
Create a one-off implementation for a protocol:
(def hello-kitty
(reify Personalized
(name [this] "Kitty")
(describe [this] "Kitty is a cat!")))
(describe hello-kitty) ; "Kitty is a cat!"Protocols can be implemented partially using reify, but calling
missing functions causes runtime exceptions.
Exercises
Shape Protocol
Define a protocol Shape with two functions area and circumference.
Hint: Use defprotocol; the functions only need a this parameter.
Test: (area (reify Shape (area [this] 0))) shall return 0.
Square and Circle Records
Implement the Shape protocol for two new record types: Rectangle
and Circle.
Hint: Use defprotocol; the Rectangle has a width and a height;
the Circle has a radius. Use Math/PI and Math/pow for
computing the circle’s area and circumference.
Test: (area (->Rectangle 3 4)) shall return 12, and
(circumference (->Circle 5)) shall return 31.41592653589793.
Volume Extension
Define a protocol Body with a function volume that expects a
height parameter. Extend the Rectangle and Circle records to
implement Body. The function shall compute the volume of a cuboid
(Rectangle) and a cylinder (Circle).
Hint: Use extend-protocol. A cuboid’s volume is the area of its
rectangular base multiplied by a height; a cylinder’s volume is the
area of its circular base multiplied by a height.
Test: (volume (->Rectangle 3 4) 5) shall return 60, and (volume (->Circle 5) 3) shall return 235.61944901923448.