New (to me) Clojure Functions
While working on a Tic Tac Toe game, I ran across a some neat Clojure functions: reduced, repeatedly, and get-in!
Short-Circuit Reduction
The reduced
function allows you to exit a reduction early if you decide you
no longer need to iterate over a collection.
The most basic use I could think of is calling reduced on the second item in an infinite sequence.
=> (reduce #(reduced %2) (range))
1
Here, reduced
allows us to perform an operation on an infinite sequence,
but only read over the first two items in the collection. This prevents us
from entering an infinite loop with range
.
In my Tic Tac Toe game, I used this in a find-first
function where I needed
only the first item matching a predicate, courtesy of the contributors on
StackOverflow.
(defn- find-first [pred coll]
(reduce #(when (pred %2) (reduced %2)) nil coll))
In this example, we only need to “realize” those elements which do not satisfy the predicate. Once we reach a value resulting in true, we can exit and return.
Repeating Yourself (in a good way)
The repeatedly
function works almost the same as repeat
, except instead of
repeating a value, it repeats a function in a lazy sequence. The resulting
sequence is a collection of the results of sequential calls to that function.
In the example below, repeatedly
is used to generate a list of timestamps,
each about 10 milliseconds apart.
=> (repeatedly 5 #(do (Thread/sleep 10) (.getTime (new java.util.Date))))
(1635378115715 1635378115725 1635378115737 1635378115747 1635378115758)
In my Tic Tac Toe game, I use this to continually prompt the user for a move
until a valid entry is entered. This allowed me to replace the loop
structure I had before and reduce the amount of code for this function.
(defn- next-move [board token]
(find-first (partial valid-move? board)
(repeatedly #(request-move token board))))
Gettin’ Default Values
The get-in
function allows you to retrieve nested values within a map, simply by placing the
key path in a vector.
=> (def gerry
{:name "Gerry"
:address {:street "101-103 Baker Street"
:city "Hot Coffee"
:state "Mississippi"
:postal-code 01134}
:phone "248-434-5508"
:email nil})
=> (get-in gerry [:address :city])
"Hot Coffee"
=> (get-in gerry [:favorite-color :rgba])
nil
=> (get-in gerry [:email])
nil
This works great for most use cases! If the key is found, then the value is returned.
If the key is not found, we get nil
.
Now what if we want to check for the existence of a key while also retrieving its value?
Both :favorite-color
and :email
will return nil
, but one key exists and the other
doesn’t. How can we know about this if both keys return nil
?
Well, get-in
provides an optional parameter for a default value if the key is not found!
This default value can return whatever we want it to, even if it makes absolutely no sense.
=> (get-in gerry [:email] :not-found)
nil
=> (get-in gerry [:favorite-color :rgba] :not-found)
:not-found
=> (get-in gerry [:favorite-color :rgba] "Once upon a time...")
"Once upon a time..."
This allows us to check for existence of a key without having going back to the map to retrieve the value.
Although, if you’re not expecting any nil
values, then the regular get-in
form will probably work just
fine.