Wakatta!

Like Eureka!, only cooler

Seven Languages in Seven Weeks Clojure Day 1

Sixth week, sixth language, this time Clojure, the latest attempt to make Lisp popular.

Clojure has the usual features of Lisp: code as data, macros, lists among many other containers (no, Lisp is not just lists), and so on. It also brings other features, such as pattern matching, and treating some containers as functions for specific purposes (both features that Arc, the language that Paul Graham invented, seems also to have). Pattern matching is certainly a welcome feature; I have to see more Clojure code to figure out whether I like the data as function one (I’m sure it allows very neat idioms).

Clojure is also supports a kind of lazy evaluation, but simpler than Haskell’s.

Finally, Clojure runs on both the JVM and the CLR, which allows it to go wherever either platform goes (which means pretty much everywhere), and to reuse these platforms’ extensive libraries.

The first day covers little, compared to other languages. Various containers are introduced, along with some useful functions operating on them; pattern matching is described (it is similar to Erlang’s and other functional languages); finally we learn how to define functions, and use them in higher-order functions like apply.

Exercises

Comparatively to previous languages’ first day, today is very short and simple.

Define function big

Nothing special here, the function count works on all collections, including strings.

Define function big
1
2
3
4
5
user=> (defn big
             "Is string st longer than n characters?"
             [st n]
             (> (count st) n))
#'user/big

Testing:

Test function big
1
2
3
4
user=> (big "hello" 5)
false
user=> (big "hello" 4)
true

Define function collection-type

Using only functions and concepts seen so far, a first implementation using map. First I use the repl to get the class of these containers:

class of containers
1
2
3
4
5
6
7
8
user=> (class ())
clojure.lang.PersistentList$EmptyList
user=> (class '(1))
clojure.lang.PersistentList
user=> (class [])
clojure.lang.PersistentVector
user=> (class {})
clojure.lang.PersistentArrayMap

So the empty list has a different class from a non-empty list.

Now I can map these classes to the proper symbol:

mapping classes to symbols
1
(def col-classes {(class '(1)) :list, (class '()) :list, (class []) :vector, (class {}) :map})

With this, getting the right answer is as simple as:

1
2
3
4
5
6
7
8
user=> (col-classes (class '()))
:list
user=> (col-classes (class '(1)))
:list
user=> (col-classes (class [1 2 3]))
:vector
user=> (col-classes (class {:one "one"}))
:map

So the function can be written as:

Define function collection-type
1
2
3
4
5
user=> (defn collection-type
             "Return either :list, :vector or :map, depending of the type of col."
             [col]
             ({(class '(1)) :list, (class '()) :list, (class []) :vector, (class {}) :map} (class col)))
#'user/collection-type

Testing it on literal values:

Test function collection-type
1
2
3
4
5
6
7
8
9
10
user=> (collection-type ())
:list
user=> (collection-type '())
:list
user=> (collection-type '(1 2 3))
:list
user=> (collection-type [1 2 3])
:vector
user=> (collection-type {:one "one"})
:map

Interesting, perhaps, but there must be a better way. Using type predicates (list?, map? and vector?), and the already seen if):

collection-type, predicate edition
1
2
3
4
5
6
7
user=> (defn collection-type
             "Return either :list, :vector or :map, depending of the type of col."
             [col]
             (if (list? col) :list
                 (if (map? col) :map
                     (if (vector? col) :vector))))
#'user/collection-type

Testing output is not repeated, as it is identical to the one above.

These nested if’s are ugly. Fortunately, Lisp has a kind of generalized switch, called cond. So the definition above is equivalent to

collection-type, cond predicate edition
1
2
3
4
5
6
7
user=> (defn collection-type
             "Return either :list, :vector or :map, depending of the type of col."
             [col]
             (cond (list? col) :list
                   (map? col) :map
                   (vector? col) :vector))
#'user/collection-type

Now this is clean and elegant.

Once again, the test returns the expected results so they are not reproduced.

One thing to note: the cons function does not return a list, but a sequence (this is unlike cons in all the other Lisps):

cons is not a team player
1
2
3
4
5
6
7
8
user=> (cons 1 '(2 3))
(1 2 3)
user=> (class (cons 1 '(2 3)))
clojure.lang.Cons
user=> (list? (cons 1 '(2 3)))
false
user=> (seq? (cons 1 '(2 3)))
true

Fortunately, assoc is better behaved:

assoc
1
2
user=> (collection-type (assoc  {:two 2} :one 1))
:map

I like the idea of a collection’s type not changing when I add element, so the behaviour of cons is something I will have to watch for (or find the better way that must exist).

And this is all for Day 1.

Comments