Clojure Pool: more things deep and shallow

I've been beginning to dig into Clojure, and this time I'll go through the third chapter of "The Joy of Clojure" 2nd ed.

Truth

0 and 1, that's what I've been tought. Clojures notion of truth is almost as simple: there are two values that are considered false, false and nil. The rest are all true.

To make something explicitly a boolean, there's the function of the same name:

user> (boolean "asdf")
true
user> (boolean nil)
false

A Python guy like me might expect the empty map or list (or vector) to be logically false, but in Clojure they're not. A list can be cast to a seq though, and an empty list will result in a nil seq, which is false:

user> (seq ())
nil
user> (seq [])
nil
user> (boolean (seq #{}))
false
user> (boolean (seq {}))
false

Destructuring

When working with Erlang I found its pattern matching to be extremely useful, even if it doesn't look like much more than syntactic sugar. Clojures' destructuring offers part of that functionality, and apparently there is more pattern matching support in the core.match library. Good times!

Destructuring can be used in function definitions and let constructs.

First, destructuring with a vector. When passing in more arguments then needed, one can capture the rest of the args with the & rest-of-args construct. This is the same as varargs with functions:

user> (let [[a & moar] (range 5)]
        (prn a)
        moar)
0
(1 2 3 4)

Note that you do not have to match the number exactly, even without & moar:

user> (let [[a] (range 5)]
        a)
0

Destructuring with functions is pretty much the same.

Maps can be destructured as well:

user> (def data {:k1 123, :k2 "foobar"})
#'user/data
user> (defn f [{k2 :k2}] k2)
#'user/f
user> (f data)
"foobar"

Here we just looked up some keys in a dict that was passed to us. This can get repetitive though. The :keys feature handles this. It extracts a vector of names:

user> (defn g [{:keys [k1]}] (+ 1 k1))
#'user/g
user> (g data)
124

Two additional features: the :as keyword binds the whole input map, and the :or feature makes it possible to provide a default other than nil in case a lookup fails:

user> (defn h [{:keys [k1 xyz] :as whole :or {xyz "dunno"}}]
        (println "k1: ", k1)
        (println "xyz: ", xyz)
        (println "all of it: ", whole))
#'user/h
user> (h data)
k1:  123
xyz:  dunno
all of it:  {:k1 123, :k2 foobar}
nil

Similarly to :keys, there is a :strs keyword for destructuring string keys and a :syms keyword for symbols.

The map destructuring constructs also work on lists. For this case, the lists are cast as a map (with every second item a key), and then destructured as above.

This is used for providing keyword args to functions:

user> (defn j [& args]
        (let [{:keys [a b c], :or {a 23 b "gugu"}} args]
          [a b c]))
#'user/j
user> (j :a "ey")
["ey" "gugu" nil]

Some more REPL doodling

Useful function to search the built-in documentation:

user> (find-doc #"\bchar\b")
...

However, most of the time I've found the online docs at http://clojuredocs.org/ and the cheat sheet (http://clojure.org/cheatsheet) easier to navigate.

Something else entirely: this is how list comprehension is done in Clojure. The for macro iterates over one or more collections and binds a local variable to each value. The body of the macro is evaluated, and a sequence of results is returned. Sounds somewhat complicated but is easy to use.

The noop comprehension:

user> (for [x '(1 2 3)] x)
(1 2 3)

Squaring:

user> (for [x '(1 2 3)] (* x x))
(1 4 9)

Use two vectors to iterate over:

user> (for [x '(1 2 3) y '("a" "b" "c")]
        (str x "-" y))
("1-a" "1-b" "1-c" "2-a" "2-b" "2-c" "3-a" "3-b" "3-c")

Again, with a little more elaborate body:

user> (for [x (range 5 7) y `(\a \b \c)]
        [x y (char (+ x (int y)))])
([5 \a \f] [5 \b \g] [5 \c \h] [6 \a \g] [6 \b \h] [6 \c \i])
 · 
peter
 ·