2. Clojure Syntax

syntax

If the syntax is good enough for the information, it should be good enough for the meta-information.
— Erik Naggum
JavaClojure
int i = 5;
(def i 5)
if (x == 1)
  return y;
else
  return z;
(if (= x 1)
  y
  z)
x * y * z;
(* x y z)
foo(x, y, z);
(foo x y z)
foo.bar(x);
(.bar foo x)

Lists

Evaluated as function calls

    (inc 1)
    ;=> 2

Invoking functions

(+ 1 2)
;=> 3

Always in prefix form

Empty list

()
;=> ()

Making lists

(cons 1 ())
;=> (1)
(cons 1 (cons 2 ()))
;=> (1 2)
(list 1 2 3)
;=> (1 2 3)

Vectors

[1 2 3 4]

Preferred over lists; easier to write

Order 1 count and lookup by index

(get [10 20 30 40 50] 3)
;=> 40

Similar to arrays, but can be added to

Vector operations

(conj [1 2 3] 4)
;=> [1 2 3 4]
(pop [1 2 3 4])
;=> [1 2 3]
(assoc [1 2 3 4] 0 5)
;=> [5 2 3 4]
(vec (list 1 2 3 4))
;=> [1 2 3 4]
(cons 0 [1 2 3 4])
;=> (0 1 2 3 4)

Equality by value

(def a [1 2 3 4])
(= a a)
;=> true
(def b [1 2 3 4])
(= a b)
;=> true
a and b are different objects
(identical? a b)
;=> false

Sequential equality

(= [1 2 3 4] (list 1 2 3 4))
;=> true

Different types, but considered equal

Symbols

  • Usually lowercase-words-hyphenated

  • Begin with an alphabet character

  • Can contain numbers and punctuation

  • Identifiers

Resolving symbols

inc
foo
(quote foo)
'foo

quote means don’t resolve or evaluate

Quote also works on lists

(quote (1 2))
;=> (1 2)
'(1 2)
;=> (1 2)

Without quote we have a problem:

(1 2)
;=> Exception: Long cannot be cast to IFn
Tried to apply 1 as a function
TypeValue
Long
1
Double
3.14
BigInteger
1000000000000N
BigDecimal
1000000000000.1M
Exponents
1e3
Ratio
2/5

Strings and characters

"This is a string."

Characters written with a backslash

\a \b \c
\newline \tab \space

Maps

{"name" "Fate of the Furious"
 "sequence-number" 8
 "rotten-tomatoes" 0.66
 "imdb" 0.67}
  • Order 1 lookup, "add", "delete" by key

  • Tuned to be fast

  • Replacement for structs/objects

  • Versatile; used often in Clojure code

Keywords

  • :my-keyword

  • Shorthand identifiers

  • Begin with a colon

  • Often used as keys in hashmaps

{:name "Fate of the Furious"
 :sequence-number 8
 :rotten-tomatoes 0.66
 :imdb 0.67}

Map operations

(get {:a 1} :a)
;=> 1
(get {:a 1} :b 2)
;=> 2
(assoc {:a 1} :b 2)
;=> {:a 1, :b 2}
(dissoc {:a 1} :a)
;=> {}

More map operations

(update {:a 2} :a inc)
;=> {:a 3}
(update {:a [1 2 3]} :a conj 4)
;=> {:a [1 2 3 4]}
(merge {:a 1} {:b 2})
;=> {:a 1, :b 2}

Commas

Commas are optional and treated as whitespace

(= {:a 1, :b 2, :c 3}
   {:a 1 :b 2 :c 3})

Prefer newlines

{:a 1
 :b 2
 :c 3}

Nesting datastructures

{:name "Fate of the Furious"
 :sequence-number 8
 :ratings {:rotten-tomatoes 0.66
           :imdb 0.67}}

Ratings are a nested map

{[1 2] {:name "diamond", :type :treasure}
 [3 4] {:name "dragon", :type :monster}}

A map with vector coordinate keys, and map values

Nested operations

(assoc-in {:a {:b {:c 1}}} [:a :b :d] 2)
;=> {:a {:b {:c 1, :d 2}}}
(update-in {:a {:b {:c 1}}} [:a :b :c] inc)
;=> {:a {:b {:c 2}}}
(get-in {:a {:b {:c 1}}} [:a :b :c])

Sets

#{1 2 3}

Near constant time lookup

(contains? #{1 2 3} 3)
;=> true

Set operations

(conj #{1 2 3} 4)
;=> #{1 2 3 4}
(disj #{1 2 3} 2)
;=> #{1 3}

union, difference and intersection are available in the clojure.set namespace

Namespaces

src/training/my_namespace.clj

(ns training.my-namespace
  (:require [clojure.set :as set])
  (:import (java.time Instant Duration)))
(set/union #{1 2 3} #{3 4})
=> #{1 2 3 4}
  • The name must match path and filename

  • my-namespacemy_namespace.clj

  • training.training/

From the REPL

(require '[clojure.set :as set])
(import
(use 'clojure.set)
(require '[clojure.set :refer :all])
Avoid use and :refer :all
ns works in the REPL!
(ns my.namespace
   (:require [clojure.set :as set]))

Programs

  • Expressions which are evaluated to results

  • If an expression needs to be compiled, it will be

  • Can be loaded from files or evaluated dynamically

  • Unit of compilation is a form

  • Nominate an entry point namespace/function

Namespaced keywords

:my.namespace/rect

Shortcut:

::rectangle
;=> :my.namespace/rectangle

:: expands to the current namespace

Defining functions

(defn square [x]
  (* x x))
(square 2)
;=> 4
(defn square
  "Multiplies a number by itself"
  [x]
  (* x x))
  (square 2)
  ;=> 4

Defining Vars

(def x 1)
;=> #'my.namespace/x
x
;=> 1
  • Global mutable reference

  • Use sparingly

  • #' means var

Accessing a var (not the value)

(var x)
;=> #'x
#'x
;=> #'x

Defining Vars continued

  • The symbol x resolves to a Var

  • Vars are automatically dereferenced when evaluated

  • Dereferrencing returns the value associated with the Var

  • Avoid using vars like variables

  • defn is actually def with a function value

  • Can use #'x or (var x) to access the Var

Let

(let [x 1]
  (inc x))
;=> 2
  • Bind symbols to values in a scope

  • Shadow existing bindings

  • Prefer let over def

Destructuring (binding forms)

(let [[x y] [1 2]]
  (+ x y))
;=> 3
  • Literal data structure containing symbols

  • Matches structure

Why destructure?

(defn normalize1 [v]
  (let [x (first v)
        y (second v)
        length (Math/sqrt (+ (* x x) (* y y)))]
    [(/ x length) (/ y length)]))

Avoid extracting substructure manually:

(defn normalize2 [[x y]]
  (let [length (Math/sqrt (+ (* x x) (* y y)))]
    [(/ x length) (/ y length)]))

Vector structures match any sequence

(let [[a b] (list 1 2)]
  b)
;=> 2

Strings and Collections (list, vector, set, map) implement seq

(seq "abc")
;=> (\a \b \c)
(seq {:a 1, :b 2, :c 3})
;=> ([:a 1] [:b 2] [:c 3])
(seq? 8)
;=> false
(let [[a b] "abc"]
  b)
;=> \b

Basic sequences

(drop 2 [0 0 0 0])
;=> (0 0)
(range 5)
;=> (0 1 2 3 4)
(take 2 "abcd")
;=> (\a \b)
  • Many sequence oriented functions

  • Never modify the original sequence

  • Often lazy

Lazy sequences

Lazy means that the next value in the sequence is only calculated when it is made use of

Stream abstraction; only the currently used item needs to be in memory

Useful for processing files that don’t fit in memory

For expressions

(for [i (range 10)]
  (* i i))
;=> (0 1 4 9 16 25 36 49 64 81)
(for [file ["a" "b" "c"]
     rank [1 2]]
  (str file rank))
;=> ("a1" "a2" "b1" "b2" "c1" "c2")

For expressions continued

(for [i (range 10)
      :when (odd? i)
      :let [square (* i i)]]
  square)
;=> (1 9 25 49 81)

Destructuring in a for expression

(let [m {:a 1, :b 2, :c 3}]
  (for [[k v] m]
    [v k]))
;=> ([1 :a] [2 :b] [3 :c])
Destructuring is available in any binding form

Variadic functions using &

(defn sub [& vs]
  vs)
(sub 1 2 3 4)
;=> (1 2 3 4)
  • Variadic means variable number of arguments

  • Arity means number of arguments

  • We could have just passed a vector instead

Apply

Calls a function with a sequence of arguments

(apply + [1 2 3 4])
;=> 10

Most mathematical functions are variadic:

(+ 1 2 3)
;=> 6

Destructuring a map

(def x {:a 10
        :b 20})
(let [{a :a, b :b} x]
  (+ a b))
;=> 30
(let [{:keys [a b]} x]
  (+ a b))
;=> 30

Destructuring strings from a map

(def y {"a" 10
        "b" 20})
  (let [{a "a", b "b"} y]
    (+ a b))
;=> 30
(let [{:strs [a b]} y]
  (+ a b))
;=> 30

Destructuring a sequence:

(def x (range 5))
(first x)
;=> 0
(rest x)
;=> (1 2 3 4)
(let [[a & more] (range 5)]
  a)
;=> 0
(let [[a & more] (range 5)]
  more)
;=> (1 2 3 4)

Nested destructuring

(def movie {:name "Fate of the Furious"
            :sequence-number 8
            :ratings {:rotten-tomatoes 0.66
                      :imdb 0.67}})
(get-in movie [:ratings :imdb])
;=> 0.67
(let [{{:keys [imdb]} :ratings} movie]
  imdb)
;=> 0.67

More destructuring

(defn f [{:keys [a b] :as x}]
  x)
(f {})
;=> {}

Destructuring with defaults

(defn f [{:keys [a b] :or {a "default"}}]
  a)
(f {})
;=> "default"
(defn f [x]
  (let [defaults {:a "default"}
        {:keys [a b]} (merge defaults x)]
    a))
(f {})
;=> "default"

Comments

Anything following a semicolon is a comment

; this is an inline comment
;; this is a function level comment

Less common is the comment form:

(comment anything)

Bug eye comments

Removal of next form #_

#_(this form is removed)
#_#_ (ignored-1) (ignored-2)
  • Temporarily remove a form when debugging code

  • Looks like a bug eyes emoji

Regex

#"pattern"
(re-seq #"\w+" "the quick brown fox")
;=> ("the" "quick" "brown" "fox")

Exercises

See manual end of section 2

Answers

Set up the new namespace called training.syntax

(ns training.syntax)

Define a var called message bound to the string "greetings"

(def message "greetings")
;=> nil

Answers

Print out the value of the var message

(prn message)
;;; "greetings"
;=> nil
prn keeps the quotes around strings; println does not

Answers

Create a let binding that binds the symbol message to "well hello there", and prints out message inside the let block:

(let [message "well hello there"]
  (prn message))
;;; "well hello there"
;=> nil

Print out message again, outside of the let block:

(prn message)
;;; "greetings"
;=> nil
message global var is still the original value

Answers

Create a let binding that destructures a map and prints the greeting and tone:

(def m {:greeting "good morning", :tone "happy"})
(let [{:keys [greeting tone]} m]
  (prn greeting tone))
;=> "good morning" "happy"

Answers

Destructure a single map input and return a string combining greeting and tone:

(defn hi [{:keys [greeting tone]}]
  (str greeting " - " tone))
(hi m)
;=> "good morning - happy"

End Syntax