Exercise 10 Runtime Polymorphism - MikeyYeahYeah/clojure-koans GitHub Wiki

Exercise 10 - Runtime Polymorphism

Arity

(defn hello
  ([] "Hello World!")
  ([a] (str "Hello, you silly " a "."))
  ([a & more] (str "Hello to this group: "
                   (apply str
                          (interpose ", " (cons a more)))
                   "!")))

In the function above, you see that you can produce different results based off the number of argument that are passed into the function. This is called arity. This allows you to create conditions based off the number of inputs passed in. For example, lines 2 and 3 show examples of 0 arity, and 1 arity. The 3rd line is a little more complex. This introduces variable-arity as demonstarted by the & more. Variable-arity will collect all or all-remaining arguments as a list and process them. In line 3, the first argument gets stored as a while any remaining arguments gets stored as a collection in more.

Function Explanations

  • str
    • converts collection to a string and interpolates variables
  • apply
  • Running a command like (str[1 2 3]) would return the string "[1 2 3]" which isn't always desirable.
  • Using apply, you can extract the actual contents of a collection meaning (apply str [1 2 3]) would return the contents of the vector"123"
  • This applies to other functions as well like max, etc...
  • interpose
    • Interpose will take a collection and inserts a specified separator between each element in the collection.
    • For example, (interpose ", " '(1 2 3)) would return (1 ", " 2 ", " 3). Notice how the collection is mixed with integers and strings (indicated by the " ").
    • We can combine apply and interpose to cleanup the result. In this case,
    • (apply str (interpose '(1 2 3))) would return "1, 2, 3"

Examples using the function above

(hello)
  ;;=> "Hello World!"

(hello "world")
  ;;=> "Hello, you silly world."

(hello "Peter" "Paul" "Mary")
  ;;=> "Hello to this group: Peter, Paul, Mary!"

Multimethods

(defmulti diet (fn [x] (:eater x)))
(defmethod diet :herbivore [a] (str (:name a) " eats veggies."))
(defmethod diet :carnivore [a] (str (:name a) " eats animals."))
(defmethod diet :default [a] (str "I don't know what " (:name a) " eats."))

This is confusing so lets break it down into smaller chunks.

  • (defmulti diet (fn [x] (:eater x)))
    • This is a multimethod function with the name diet
    • It contains an anonymous function: (fn [x] (:eater x))
      • This function will grab the value assigned to :eater from a map passed to diet function
  • (defmethod diet :herbivore [a] (str (:name a) " eats veggies."))
    • This creates a condition for the multimethod. In this case the condition must match :herbivore
    • The contents of the item passed to diet get assigned to a
    • In this example, a map is being passed to a which allows you to access the values assigned to a particular key IE (:name a)
    • There is a special keyword called :default which acts as a catch all
(diet {:species "deer" :name "Bambi" :age 1 :eater :herbivore}))
  ;;=> "Bambi eats veggies."
  
(diet {:species "rabbit" :name "Thumper" :age 1 :eater :herbivore}))
  ;;=> "Thumper eats veggies."

(diet {:species "lion" :name "Simba" :age 1 :eater :carnivore}))
  ;;=> "Simba eats animals."
  
(diet {:name "Rich Hickey"})))
  ;;=> "I don't know what Rich Hickey eats."