25% developed

Learning Clojure

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Some paragraphs in [ ] are author notes. They will be removed as the page matures. You should be able to read the text OK if you ignore these notes.

This Book is currently being restructured to better satisfy the wikibooks standard. Until completed, duplicate content may be seen.

For detailed coverage of Clojure, consult the language and API reference at clojure.org. Also see Clojure Programming.

Clojure (read as closure), is a powerful, lisp-1 programming language designed by Rich Hickey and designed to run on the Java Virtual Machine. This book shall provide a detailed introduction to the constructs of Clojure, and act as a tutorial through the features of the language.

Getting Started[edit | edit source]

History Development stage: 20%
A brief history of the Clojure language.
Installation Development stage: 60%
Learn how to install Clojure on Windows, Linux, and Mac
REPL Development stage: 10%
Learn how to launch the REPL execution system and the classic "Hello World" application

Basics[edit | edit source]

Basic Syntax Development stage: 00%
Describes the basics in how the applications you write will be interpreted
Coding Conventions Development stage: 70%
Quickly describes the generally accepted conventions for Clojure.
Functional Programming Development stage: 70%
The entities used to store data of various shapes
Namespaces Development stage: 00%
Identifies how to encapsulate a program from other programs
Basic Operations Development stage: 50%
Explains how use essential functions
Data Types Development stage: 90%
Explanation of basic data types in the Clojure language.
Data Structures Development stage: 70%
Explains how to use Clojure hash, list, and array data structures
Meta Data Development stage: 80%
Explaining meta data in the Clojure language.
Special Forms Development stage: 70%
Learn how to use several utility functions to support Clojure
Branching and Monads Development stage: 00%
How to implement imperative style into a functional language
Exception Handling Development stage: 00%
Responding to errors that can occur
Evaluation Development stage: 40%
How expressions are evaluated

Java Integration[edit | edit source]

Calling Java Development stage: 70%
Learn how to call Java functions from within Clojure
Building Jars Development stage: 00%
Learn how to take Clojure code and create a portable Java executable

Advanced Topics[edit | edit source]

Macros Development stage: 60%
Learn how to utilize the macro system Clojure uses
Concurrent Programming Development stage: 10%
Learn how to write programs that utilize concurrent execution
Leiningen Development stage: 10%
Learn the perfected Clojure build system
Reader Macros Development stage: 70%
Macros that control the way code is interpreted
Unit Testing Development stage: 00%
Learn how to write unit tests in Clojure

More Data Structures[edit | edit source]

[transfered to new layout]

Sets[edit | edit source]

[transfered to new layout]

A set is a collection containing no duplicate items. Clojure has two set types:

  • a hash set is implemented as a hashmap and so has (near) constant lookup, insertion, and removal times.
  • a sorted set is implemented as an ordered binary tree and so has logarithmic lookup, insertion, and removal times.

The reader recognizes a literal syntax for hash sets:

#{67 2 8.8 -78}     ; a hash set of four numbers

Lazy sequences[edit | edit source]

[transfered to new layout]

Many sequence functions produce lazy sequences, which are sequences not actually backed by their own data: the items of a lazy sequence are produced on request from a function or retrieved from some other source, such as another collection. For example, the sequence representing the first 8 items of a pre-existing vector can be lazy because there's no need to copy the items: if we, say, request the 3rd item of the lazy sequence, we get back the 3rd item of the vector.

A lazy sequence based on a function produces items based on the index passed to the function. Because such sequences are backed by no actual data at all, they can be infinite in size. For instance, the function cycle returns a lazy sequence representing the endless repetition of another sequence:

(cycle [1 2 3])   ; returns a lazy sequence of 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3... etc.

[hmm, under what scenarios does a lazy seq keep around the items as they're produced? I assume a cycle doesn't end up backed by a huge actual list or vector, but doseq does seem to keep around the produced values. What other cases are like doseq?]

StructMaps[edit | edit source]

[transfered to new layout]

What Clojure calls a structmap (as in "structure-map") is simply a variant of a persistent hashmap, but one in which a pre-defined set of keys have optimized storage and lookup.

A structmap is created by first calling clojure/create-struct to define a blueprint called a basis. The function clojure/struct-map is then used to create actual structmaps using a basis:

(def george (create-struct :apple :banana :orange))
(struct-map george :apple 3 :banana 9 :orange 12)     ; create a structmap with the key-val pairs :apple => 3, :banana => 9, and :orange => 12
(struct-map george :banana 9 :apple 3 :orange 12)     ; notice the key order need not be that used in create-struct
(struct-map george :apple 3 :orange 12)               ; the key :banana is not specified, so its value defaults to nil

You can create a structmap by specifying only the values for the keys using clojure/struct:

(struct george -87 0 9)      ; keys implied by their order in create-struct, so this is :apple => -87, :banana => 0, :orange => 12

Multimethods and polymorphism[edit | edit source]

Most languages treat encapsulation and implementation inheritance as the primary features of object-oriented programming, but Clojure thinks these things are overrated. The real virtue of OOP, Clojure says, is polymorphism---encapsulation and inheritance are simply straight-jackets holding back polymorphism's potential.

When doing object-oriented programming in Clojure, we simply use structmaps in place of objects while, in place of traditional encapsulated methods, we use what Clojure calls multimethods, functions which pass on their arguments to other functions based on some criteria of the arguments, such as the number and/or type of the arguments.

A multimethod is made up of three things: a dispatch function, a set of methods, and a set of values associated with those methods. Calling a multimethod calls the dispatch function, and the value returned determines which method to call. A multimethod is created with clojure/defmulti and methods added to it by the macro clojure/defmethod. Here's a reductively simple multimethod with just two methods:

(defmulti victor (fn [a] a))
(defmethod victor 3 [a] "hello")   ; attach a method to call when the dispatch returns 3; the function takes 1 argument, 
                                   ; which it ignores, returning "hello"
(defmethod victor 5 [a] "goodbye")
(victor 3)                         ; returns "hello"
(victor 5)                         ; returns "goodbye"
(victor 4)                         ; exception: No method for dispatch value: 4

Note that the methods' arities must match the multimethod arity because the dispatch function calls the methods by passing on its arguments. (Also note the stylistic inconsistency: defmulti expects a function argument whereas defmethod imitates the defn form.)

Multimethods can be used to imitate traditional single- and multiple-inheritance polymorphism as well as more flexible kinds of polymorphism.

Collection functions[edit | edit source]

The clojure namespace contains many functions and macros for handling collections. Rather than repeat reference information from the API documentation, I'll give just a few example operations:

  • (count collection)

Returns the number of items in the collection.

(count [a b c])  ; return 3
(count nil)      ; return 0
  • (conj collection item)

conjoin. Returns a new copy of the collection with the item added. How the item is added depends upon the collection type, e.g. an item conjoined to a list is added to the beginning while an item conjoined to a vector is added to the end.

(conj [5 7 3] 9)        ; returns [5 7 3 9]
(conj (list 5 7 3) 9))  ; returns list (9 5 7 3)
(conj nil item)         ; returns list (item) 
  • (list item*)
  • (vector item*)
  • (hash-map keyval*) where keyval => key value

Produce a list, vector, or hashmap, respectively. You can generally always just use literal syntax in place of these functions, but having functions gives us something to pass to other functions expecting function arguments.

  • (nth collection n)

Return the nth item from collection, where collection is any collection but not a map. (The collection can be a sequence over a map, but not an actual map.)

(nth [31 73 -11] 2)        ; returns -11  
(nth (list 31 73 -11) 0)   ; returns 31
(nth [8 4 4 1] 9)          ; exception: out of bounds
  • keywords as functions

A keyword can be used as a function to look up a key's value in the various kinds of maps (hashmaps, sets, structmaps, etc.):

(:velcro {:velcro 5 :zipper 7})   ; returns 5, the value mapped to :velcro in the hashmap
  • maps as functions

The map types themselves can be used as functions to look up their key's values:

({:velcro 5 :zipper 7} :velcro)   ; returns 5, the value mapped to :velcro in the hashmap
({3 "cat" :tim "moose"} :tim)     ; returns "moose", the value mapped to :tim in the hashmap
(["hi" "bye"] 1)                  ; returns "bye", the value of the second index in the vector

Sequence functions[edit | edit source]

Most of the sequence functions can be passed objects which are not sequences but from which a sequence can be derived, e.g. you can pass a list in place of a sequence.

Where there are redundancies between sequence operations and more operations specialized for specific collection types, the redundancies are mostly for performance considerations. For instance, to produce a reversed-order sequence of a vector, you can use the sequence operation reverse or the vector specific rseq: while rseq is less general, it is more efficient.

  • (seq collection)

Return a sequence representing the collection. seq also works on native Java arrays, Strings, and any object that implements Iterable, Iterator, or Enumeration.

Destructuring[edit | edit source]

The special forms let, loop, and fn are actually macros: the real special forms are named let*, loop*, and fn*. The macros are most commonly used in place of the actual special forms because the macros add a convenience feature called destructuring.

Often a function expects to receive a collection argument out of which it means to use particular items:

; return the sum of the 1st and 2nd items of the sequence argument
(defn foo [s]
   (+ (first s) (frest s))) ; sum the first item and the "frest" (first of the rest) item

Having to extract out the values we want can get quite verbose, but using destructuring can help in many cases:

; return the sum of the 1st and 2nd items of the sequence argument
(defn foo [s]
  (let [[a b] s]
    (+ a b)))

Above, where a parameter name is normally specified in the argument list, we put a vector followed by a parameter name: the items in the collection passed to the parameter s are bound to the names a and b in the vector according to position. Similarly, we can destructure using a map:

(def m {:apple 3 :banana 5})
(let [{x :apple} m]            ; assign the value of :apple in m to x
   x)                          ; return x

[I have to say this feels backwards to me: shouldn't it be (let [{:apple x} m] x)? I like to think of the destructuring expression as mirroring the collection being destructured.]

Destructuring can also be used to pull out the value of collections within collections. Hickey gives more complete coverage here.

Atom[edit | edit source]