Clojure: Part II

!

Data in Clojure

$\mathbf{D}$(clojure) = a sequence of (nested) data

(1 2 3)
("abc" "def")
["abc" ("def" 1 2 3)]

!

Let’s explore the definitions of data.

Data in Clojure

Atoms

  • Numbers: 3.1415, 100, 2/3, 100N, 3.1415M
  • Strings: "hello world"
  • Characters: \a \b \c
  • Symbols: name, π
  • Keywords: :name
  • Nil value: nil

Lists

( ... )

Lists are linked lists.

Lists

Important

!

(println "hello" "world")

! This is a function invocation.

(quote (println "hello" "world"))

! This is a list of three elements.

'(println "hello" "world")

! This is the preferred way of quoting a list.

Vectors

[ ... ]

Vectors are arrays.

Hash-maps

{ <key> <value> <key> <value> ... }

Hash-map are hash maps :)

Hash-maps

Idiomatic Clojure prefers to use keywords as keys.

  • It makes the code readable.

  • Keyword lookup is also more efficient than strings.

!

{:name   "Ken Pu"
 :office "UA4041"
 :role   :instructor}

Sets

#{ ... }

Duplicates are not allowed. Order doesn’t matter.

(= #{3 2 1} #{1 2 3}) ; true

An example

{:type "a relational database"
 :name "csci3055u assignments"
 :tuples [ {:name "assignment 1"
            :due-date ("October" 21 2016)
            :url "http://..."}

           {:name "assignment 2"
            :due-date ("November" 20 2016)
            :url "http://..."}

           {:name "assignment 3"
            :due-date ("December" 6 2016)
            :url nil}
         ]
}

! A data value that describes a relational table.

Functional Programming in Clojure

!

This is really important.

Functional Programming in Clojure

Data is (almost always) immutable.

Example:

!

Consider data:

{:name "Albert Einstein"
 :profession "Patent Clerk"
 :age 24
 :publication ["Relativity"
               "Speed of light"]}

!

How can we update the :profession field, or age?

  • We cannot.
  • Clojure only supports convenient ways for us to to create a copy of the data with a modified :profession field.

Preview of what’s to come…

(def person {:name "Albert Einstein"
             :profession "Patent Clerk"
             :age 24
             :publication ["Relativity"]})
(assoc person :profession "Physicist")

! This does not change the person (as it cannot be changed - ever). Instead, (assoc ...) creates a new clone with the delta change of setting :profession being Physicist.

(update person :age inc)

! This makes a copy of person, with the change of incremental :age by one.

Functional programming

Programs in Clojure

!

Function invocations

General form:

( <fn> <arg> ... )

Examples:

(list 1 2 3)
; (1 2 3)

! list constructs a list consisting of its arguments.

(vector 1 2 3)
; [1 2 3]

! vector is like list, but returns a vector.

(range 10)
; (0 1 2 3 4 5 6 7 8 9)

! Returns a list of a sequence of number.

Function invocation - aka function application

We are going to meet our first higher order function: apply.

(apply <func> <arg-list>)

is equivalent to:

(<func> <arg> <arg> ...)

Function declaration

(defn f [args...] body)

! Binding a function to a symbol as in the global scope.


(fn [args ...] body)

! Creating an anonymous function.


#( ... )

! Creating an anonymous function. In the local scope, the input arguments are bound to the symbols %1, %2, etc. If there is only one argument, then it’s bound to %.

Function declaration

Recommended way:

(defn add [x y]
  (+ x y))

More indirect, but still quite readable:

(def add
  (fn [x y] (+ x y)))

Not recommended for this:

(def add #(+ %1 %2))

! Irresponsible show-off of deep knowledge of Clojure syntax can quickly turn into unreadable spaghetti code.

More on function declarations: variadic arguments

A function can have multiple versions:

(defn f
  ([arg] body)
  ([arg arg] body)
  ([arg arg arg] body))

A function can also have non-deterministic number of arguments, and bind all the arguments in an array.

(defn f [arg arg & args] body)

! In the scope of body, the symbol args is bound to a list consisting of the third, fourth, fifth arguments, and so on.

Branching

(if <cond> <expr> <expr>)

Example:

(defn larger [x y]
  (if (< x y) y x))
(defn largest [& xs]
  (if (= (count xs) 1)
    (first xs)
    (larger (first xs) (apply largest (rest xs)))))

Branching: case by case

(cond
  <cond> <expr>
  <cond> <expr>
  ...
  :else <expr>)

Example:

(defn compare [x y]
  (cond
    (< x y) -1
    (> x y) 1
    :else   0))

DO block

(do expr
    expr
    ...
    expr)

Evaluates every expression in the (do ...) form. The (do ...) form evaluates to the last expression.

Example:

(def x (do (println "Hello there.")
           (+ 1 2 3)))
; 6

Iteration: a first look

(for [i expr] body)
  1. expr must evaluate to a sequence of elements.
  2. i is the symbol that exists in the scope of body. It is bound to elements in expr during each iteration.
  3. At each iteration body is evaluated.
  4. !

Note:

(for ...) is an expression. It is evaluated to a lazy sequence, consisting of elements of each evaluation of body.

For-loop

Example:

(for [i (range 10)]
  (str "Hi, this is the " i "th iteration."))

For-loop super charged

(for [sym seq-expr :when cond :while cond] body)

Example:

(for [x (range 100)
      y (range 100)
      :when (= (+ x y) 42)]
  (println x y))

More iterations:

(doseq [i expr] body)

It is very similar to (for [i expr] body), but it’s non-functional.

(doseq ...) iterates over expr, and evaluates body for each iteration.

! But the result of each evaluation is THROWN AWAY

More iterations

(dotimes [i n] body)

This is equivalent to:

(doseq [i (range n)] body)

Summary

!


Things to come:

  1. Data structural transformations
  2. Sequences and lazy evaluation
  3. Looping through recursion
back
© KEN PU, 2016