Symbols

Clojure scoping rules

Resolution

Namespaces

Polymorphism

Definition: Polymorphism

The implementation of a function is determined at runtime based on the argument types.

!

Example

(defn parking-fee [person num-months] ...)

There are two types of persons:

  • Faculty members, who pay $2000.00 / year for parking
  • Students, who pay $40.00 / month for parking

What is the best way to implement parking-fee?

! Polymorphism is the solution…

An non-polymorphic solution

Use branching if, cond, case, …

(defn parking-fee [person num-months]
  (case (:type person) 
    :faculty (* (/ 2000.0 12) num-months)
    :student (* 40 num-months)
    nil))

This is bad for several reasons?

  • How many different types of person are there?
  • How much duplication in abstraction?
  • There is a single point failure.
  • How do we maintain consistency when new person type is added? !

Consider the matrix of dependencies:

Function :student :faculty :staff :visitor
parking-fee
tuition

!

Polymorphism using multimethod

(defmulti <fn> <dispatcher>)

...

(defmethod <fn> [dispatching-values...]
  [arg...]
  (__ body __))
  
(defmethod <fn> [dispatching-values...]
  [arg...]
  (__ body __))

Example

(defmulti parking-fee (fn [person num-months] (:type person)))

; faculty parking-fee rate is $2000/year
(defmethod parking-fee :faculty
  [person num-months]
  (* num-months (/ 2000.0 12)))

; student parking-fee rate is $40/month
(defmethod parking-fee :student
  [person num-months]
  (* num-months 40.0))

; no other types are supported
(defmethod parking-fee :default
  [person num-months]
  nil)

Example

; ============================
; test the code
; ============================

(let [einstein {:type :faculty
                :name "Albert Einstein"}
      jack {:type :student
            :name "Jack"}]
  (doseq [person [einstein jack]]
    (printf "%s pays $%.2f for 5 months of parking.\n" 
            (:name person)
            (parking-fee person 5))))
Albert Einstein pays $833.33 for 5 months of parking.
Jack pays $200.00 for 5 months of parking.

Protocols

!

Clojure’s solution to interfaces

Protocols

Protocol is a heavy-lifting language feature, many of which are impossible to do in a language like Java.

  1. We can define types and records that implement one or more protocols.

  2. We can extend an existing types to implement a protocol.

  3. We can create an instance that implements one or more protocols without declaring a type.

Defining the protocol

(defprotocol <name>
    (<fun-name> [ <args> ... ])
    (<fun-name> [ <args> ... ])
    ...)

Example

(defprotocol IPayee
    (parking [person months])
    (tuition [person credits]))

!

How do we create implementations of IPayee?

; total cost of completing a 40-credit semester in
; 4 months.
(let [x (... make-a-payee ...)]
    (+ (parking x 4) (tuition x 40)))

Type

!

Clojure’s solution for classes

Creating a type

A type consists of:

  1. a constructor
  2. a list of protocols that it implements
  3. the set of functions

!

(deftype <type-name> [<args> ...]
  <protocol>
  (<method> [args ...] ( ... body ... ))
  (<method> [args ...] ( ... body ... ))
  ...)

Example: implementation

!

(defprotocol IPayee
  (parking [person months])
  (tuition [person credits]))

!

A type for faculty

(deftype Faculty [name]
  IPayee
  (parking [person months]
    (if (= name "Albert Einstein")
      0
      (* months (/ 2000.0 12))))
  (tuition [person credits]
    (if (= name "Richard Feynman")
      0
      (* 1.0 credits))))

A type for student

(deftype Student [name]
  IPayee
  (parking [person months] (* months 40.0))
  (tuition [person credits] (* credits 100.0)))

Example: data

Constructor functions are automatically generated and bound to symbols <class-name>.

(let [albert (Faculty. "Albert Einstein")] ...)

Example: data

!

(deftype Faculty [name]
  IPayee
  (parking [person months]
    (if (= name "Albert Einstein")
      0
      (* months (/ 2000.0 12))))
  (tuition [person credits]
    (if (= name "Richard Feynman")
      0
      (* 1.0 credits))))
(deftype Student [name]
  IPayee
  (parking [person months] 
    (* months 40.0))
  (tuition [person credits] 
    (* credits 100.0)))

!

(let [p1 (Faculty. "Albert Einstein")
      p2 (Faculty. "Richard Feynman")
      p3 (Student. "Jack")]
  (println "P1" (parking p1 5))
  (println "P2" (parking p2 5))
  (println "P3" (parking p3 5)))

!

Issues (or features)

In the scope of methods, we can access the name symbol (and its binding).

But outside the scope, we cannot access the name symbol.

Records

!

Clojure’s solution to classes with properties

Records

Records are defined like types, but behaves (almost) like hash-maps.


Declaration of record type

(defrecord <name> [args ...]
  <protocol>
  (method ...)
  (method ...)
  ...)

Example:

!

The type implementation

(deftype Faculty [name]
  IPayee
  (parking [person months]
    (if (= name "Albert Einstein")
      0
      (* months (/ 2000.0 12))))
  (tuition [person credits]
    (if (= name "Richard Feynman")
      0
      (* 1.0 credits))))

!

The record implementation

(defrecord Faculty [name]
  IPayee
  (parking [person months]
    (if (= name "Albert Einstein")
      0
      (* months (/ 2000.0 12))))
  (tuition [person credits]
    (if (= name "Richard Feynman")
      0
      (* 1.0 credits))))

Example:

(defn report [person months credit]
  (println (:name person) 
           "needs to pay:"
           (+ (parking person months)
              (tuition person credits))))

Records act just like hashmaps:

(let [person (Student. "Jack")]
  (assoc person :name "Jim"
                :grade "A+"))

Extending existing types

!

Adhoc patching up existing classes

Compare to Java

defprotocol = Interfaces

deftype = Classes with private members

defrecord = Classes


extend-type, extend-protocol have no equals in Java.

Extend

What is a type?

A type is a Java class or a previously defined Clojure type by deftype or defrecord.

!

Given a type T, we can make it implement one or more protocols with extend-type.

(extend-type T
    <protocol>
    (<method> [...] (...body...))
    (<method> [...] (...body...))
    ...

    <protocol>
    ...)

Example

Can a string be a payee? Sure.

(extend-type java.lang.String
  IPayee
  (parking [s months] (* months (Float/parseFloat s)))
  (credits [s credits] (* credits (Float/parseFloat s))))

!

Wow, we can do some crazy stuff:

(parking "3.14" 4)
(tuition "6.2" 40)

Reify

!

re·i·fy
/ˈrēəˌfī/

verb
make (something abstract) more concrete or real.
“these instincts are, in humans, reified as verbal constructs”

Example

(reify
  IPayee
  (parking [x months] ...)
  (tuition [x credits] ...))

Summary

!

back
© KEN PU, 2016