Finding the average of ages
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 that 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? Consider the following code snippet:
(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 that we're back at square one! It's worse now, because using some->
doesn't help if we need to use all of the 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 whether all of the 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 that we will look at, applicative functors, will fix this.