Clojure is a functional programming language with support for parallel computation. Clojure is installed in the lab: open terminal, type "clojure" on the command line.
(+ 1 2 3) ; just like Scheme
;; function definition
(defn average [x y] (/ (+ x y) 2))
;; function call
(average 2 5)
;; a function that returns a function
(defn addx [x] (fn [y] (+ x y)))
;; using a map to add 5 to all elements of a list
;; recall the quote notation for lists
(map (addx 5) '(1 2 3 4 5))
;; expected value:
'(6 7 8 9 10)
;; passing a "lambda" (anonymous) function to map
;; %1 means "the first argument"; you can have %2, %3, etc.
(map #(+ %1 5) '(1 2 3 4 5))
;; vectors
[1 2 3]
;; functions are applicable to any sequence (list, vector, etc)
(map #(+ %1 2) [1 2 3]) ;; results look like a list, really are a sequence
;; lazy opertaions: execute as little as possible to get the result
(nth (range 1 50) 2)
(nth (range 1 100000000000000) 2)
;; structures are called "maps" in Clojure and have
;; a different syntax:
;; a "structure" with a name and a gpa fields
;; terminology: :name is a key, 'Sally is a value
;; a structure created "on the fly"
(def Sally {:name 'SallySmith :gpa 3.0})
(:name Sally) ; selector
(:gpa Sally) ; selector
;; defining a structure type (struct in Scheme)
(defstruct student :name :gpa)
(struct-map student :name 'BobBrown :gpa 2.9)
nil ;; a value that represents an empty list; also means "false" in cond
;; strings are Java string; you can call Java methods on them
;; in Java you would write mystring.split(), mystring.toUpperCase
(map (fn [x] (.toUpperCase x)) (.split "Dasher Dancer Prancer" " "))
;; functions of varying number of arguments
(def min12 (fn ([x] x)
([x y] (if (< x y) x y))
))
(min12 4)
(min12 2 3)
;; Examples from
;; http://en.wikibooks.org/wiki/Clojure_Programming/By_Example
;; there is a special construct for tail recursion: recur
(defn factorial [n]
(defn fac [n acc]
(if (zero? n)
acc
(recur (- n 1) (* acc n)))) ; recursive call to fac,
; but reuses the stack; n will be (- n 1), and acc will be (* acc n)
(fac n 1))
;; loops are written using recur
(loop [cnt 5 acc 1]
(if (zero? cnt)
acc
(recur (dec cnt) (* acc cnt))))
;;;; Concurrency (parallel programming)
;; Clojure uses Java threads. It provides different types of shared
;; mutable data and operations for modifying these data in a safe way
;; The three types are: refs (references), agents, and atoms
;; Refs are shared among threads and are modified in a transaction.
;; A transaction is an all-or-nothing sequence of steps: either all
;; steps are done with the exclusive ownership of the data, or none
;; are done, and the transaction will retry later.
;; More here:
;; http://clojure.org/refs
;; define a reference r with a value 6
(def r (ref 6))
;; an update must be done in a transaction "dosync"
(dosync (ref-set r 5))
;; get the value in r
@r
;; Agents: asynchronous safe modification
;; Unlike refs, where the thread will wait for dosync to finish,
;; an agent sends a request for a modification, and the program
;; continues.
;; Functions that modify an agent are queued and execute one at a
;; time when the agent becomes available.
;; define an agent "a" with a value 1
(def a (agent 1))
;; "send" an increment request to a (add 1 to a)
(send a inc)
;; wait for all requests to a (at the time of await) to finish
(await a)
;; get the value in a
@a
;; Atoms are modified in a synchronous manner, i.e. a thread with a
;; request to change an atom will wait for the function to return.
;; A request for change is applied without "locking" the atom.
;; If another thread has changed the atom after the modifying function
;; started and before it returned then the function is retried.
;; Therefore functions applied to atoms cannot have side affects
;; on anything other than the atom itself
;; define an atom atm with a value 1
(def atm (atom 1))
;; swap! is sending an increment request to atm
(swap! atm inc)
;; get the value of the atom
@atm