In this section, we will explore a different use case for the Option
Functor. We would like to, given a number of pirates, calculate the average of their ages. This is simple enough to do:
(defn avg [& xs] (float (/ (apply + xs) (count xs)))) (let [a (some-> (pirate-by-name "Jack Sparrow") age) b (some-> (pirate-by-name "Blackbeard") age) c (some-> (pirate-by-name "Hector Barbossa") age)] (avg a b c)) ;; 56.666668
Note how we are using some->
here to protect us from nil
values. Now, what happens if there is a pirate for which we have no information?
(let [a (some-> (pirate-by-name "Jack Sparrow") age) b (some-> (pirate-by-name "Davy Jones") age) c (some-> (pirate-by-name "Hector Barbossa") age)] (avg a b c)) ;; NullPointerException clojure.lang.Numbers.ops (Numbers.java:961)
It seems we're back at square one! It's worse now because using some->
doesn't help if we need to use all values at once, as opposed to threading them through a chain of function calls.
Of course, not all is lost. All we need to do is check if all values are present before calculating the average:
(let [a (some-> (pirate-by-name "Jack Sparrow") age) b (some-> (pirate-by-name "Davy Jones") age) c (some-> (pirate-by-name "Hector Barbossa") age)] (when (and a b c) (avg a b c))) ;; nil
While this works perfectly fine, our implementation suddenly had to become aware that any or all of the values a
, b
, and c
could be nil
. The next abstraction we will look at, Applicative Functors, fixes this.
3.145.14.132