Skip to content

Clojure Syntax in 15 minutesλ︎

A quick tour of the Clojure syntax, which is so terse you can read through this section in around 15 minutes (or less).

This overview is expanded throughout the Practicalli Learning Clojure book along with practical examples.

Commentsλ︎

;; two semi-colons for a line comment, ; single semi-colon to comment the rest of the line

#_ comment reader macro to comment out the next form

(comment ) form to comment all the containing forms

Clojure written in formsλ︎

Clojure is written as "expressions", an expressions being a lists of elements (forms) inside parentheses, (), separated by a space character (or multiple spaces / lines).

Clojure evaluates the first element in an expression as a function call. Additional values in the expression are passed as arguments to the called function.

Clojure is organised into one or more namespaces. The namespace represents the directory path and file name that contains the code of the particular namespace.

;; Define the namespace test
(ns test.code)  ;; src/test/code.clj

;; Define a longer namespace
(ns com.company.product-name.component.service)  ;; src/com/company/product_name/component/service.clj

Kebab-case for Clojure, Snake-case for file_names

Lisps use kebab-case, words joined by -, when names are composed of multiple words.

File and directory names must use snake_case, words joined by _, as the underlying Java host does not support - dash characters in names.

String manipulationλ︎

The str function creates a new string from all the arguments passed

(str "Hello" " " "World")
; => "Hello World"

clojure.string returns string values (other functions my return characters as results)

Math, Truth & prefix notationλ︎

Functions use prefix notation, so you can do math with multiple values very easily

(+ 1 2 3 5 7 9 12) ; => 40
(- 24 7 3) ; => 14
(* 1 2) ; => 2
(/ 27 7) ; => 22/7

Math is very precise, no need for operator precedence rules (as there are no operators)

Nesting forms defined a very precise calculation

(+ 1 (- 3 2)) ; = 1 + (3 - 2) => 2

Equality is =

(= 1 1) ; => true
(= 2 1) ; => false

true and false are Boolean values

clojure (true? true) ; => true (not true) ; => false (not= true false) ; => true (true? (complement true?)) ; => false

Typesλ︎

Clojure is strongly typed, so everything is a type in Clojure.

Clojure is dynamically typed, so Clojure infers the type. A type does not need to be specified in the code, making the code simpler and more concise.

As Clojure is a hosted language it uses the type system of its host where relevant. For example, Clojure uses Java object types for booleans, strings and numbers under the covers.

Use class or type functions to inspect the type of some code in Clojure.

(class 1) ; Integer literals are java.lang.Long by default
(class 1.); Float literals are java.lang.Double
(class ""); Strings always double-quoted, and are java.lang.String
(class false) ; Booleans are java.lang.Boolean
(class nil); The "null" value is called nil

Vectors and Lists are java classes too!

(class [1 2 3]); => clojure.lang.PersistentVector
(class '(1 2 3)); => clojure.lang.PersistentList

Collections & Sequencesλ︎

The most common data collections in Clojure:

  • (1 2 "three") or (list 1 2 "three") - a list of values read from start to end (sequential access)
  • [1 2 "three"] or (list 1 2 "three") - a vector of values with index (random access)
  • {:key "value"} or (hash-map :key "value") - a hash-map with zero or more key value pairs (associative relation)
  • #{1 2 "three"} or (set 1 2 "three") - a unique set of values

A list () is evaluated as a function call. The first element of the list the name of the function to call and additional values are arguments to the function.

The ' quote function informs the Clojure reader to treat the list as data only.

'(1 2 3)

Lists and vectors are collections

(coll? '(1 2 3)) ; => true
(coll? [1 2 3]) ; => true

Only lists are sequences

(seq? '(1 2 3)) ; => true
(seq? [1 2 3]) ; => false

Sequences are an interface for logical lists, which can be lazy. "Lazy" means that a sequence of valus are not evaluated until accessed.

A lazy sequence enables the use of large or even an infinite series, like so:

 (range) ; => (0 1 2 3 4 ...) - an infinite series
(take 4 (range)) ;  (0 1 2 3) - lazyily evaluate range and stop when enough values are taken

Use cons to add an item to the beginning of a list or vector

(cons 4 [1 2 3]) ; => (4 1 2 3)
(cons 4 '(1 2 3)) ; => (4 1 2 3)

Use conj to add an item relative to the type of collection, to the beginning of a list or the end of a vector

(conj [1 2 3] 4) ; => [1 2 3 4]
(conj '(1 2 3) 4) ; => (4 1 2 3)

Use concat to add lists or vectors together

(concat [1 2] '(3 4)) ; => (1 2 3 4)

Use filter, map to interact with collections

(map inc [1 2 3]) ; => (2 3 4)
(filter even? [1 2 3]) ; => (2)

Use reduce to reduce them

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

Reduce can take an initial-value argument too

(reduce conj [] '(3 2 1))
; => [3 2 1]

Equivalent of (conj (conj (conj [] 3) 2) 1)

Functionsλ︎

Use fn to create new functions that defines some behaviour. fn is referred to as an anonymous fuction as it has no external name to be referenced by and must be called within a list form.

(fn hello [] "Hello World") ; => hello

Wrap a (fn ,,,) form in parens to call it and return the result

((fn hello [] "Hello World")) ; => "Hello World"

Create a reusable function using def, creating a name that is a var. The function behaviour defined in def can be changed and the expression re-evaluated to use the new behaviour.

(defn hello-world []
  "Hello World")
;; => "Hello World"

The [] is the list of arguments for the function.

(defn hello [name]
  (str "Hello " name))
(hello "Steve") ; => "Hello Steve"

Clojure supports multi-variadic functions, allowing one function definition to respond to a function call with different number of arguments

(defn hello3
  ([] "Hello World")
  ([name] (str "Hello " name)))
(hello3 "Jake") ; => "Hello Jake"
(hello3) ; => "Hello World"

Functions can pack extra arguments up in a seq for you

(defn count-args [& args]
  (str "You passed " (count args) " args: " args))
(count-args 1 2 3) ; => "You passed 3 args: (1 2 3)"

You can mix regular and packed arguments

(defn hello-count [name & args]
  (str "Hello " name ", you passed " (count args) " extra args"))
(hello-count "Finn" 1 2 3)
; => "Hello Finn, you passed 3 extra args"

Hash-map collectionsλ︎

(class {:a 1 :b 2 :c 3}) ; => clojure.lang.PersistentArrayMap

Keywords are like strings with some efficiency bonuses

(class :a) ; => clojure.lang.Keyword

Maps can use any type as a key, but usually keywords are best

(def stringmap (hash-map "a" 1, "b" 2, "c" 3))
stringmap  ; => {"a" 1, "b" 2, "c" 3}

(def keymap (hash-map :a 1 :b 2 :c 3))
keymap ; => {:a 1, :c 3, :b 2} (order is not guaranteed)
Commas are whitespace

commas are always treated as whitespace and are ignored by the Clojure reader

Retrieve a value from a map by calling it as a function

(stringmap "a") ; => 1
(keymap :a) ; => 1

Keywords can be used to retrieve their value from a map. Strings cannot be used.

(:b keymap) ; => 2

("a" stringmap)
; => Exception: java.lang.String cannot be cast to clojure.lang.IFn

Retrieving a non-present value returns nil

(stringmap "d") ; => nil

Use assoc to add new keys to hash-maps

(assoc keymap :d 4) ; => {:a 1, :b 2, :c 3, :d 4}

But remember, clojure types are immutable!

keymap ; => {:a 1, :b 2, :c 3}

Use dissoc to remove keys

(dissoc keymap :a :b) ; => {:c 3}

Setsλ︎

(class #{1 2 3}) ; => clojure.lang.PersistentHashSet
(set [1 2 3 1 2 3 3 2 1 3 2 1]) ; => #{1 2 3}

Add a member with conj

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

Remove one with disj

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

Test for existence by using the set as a function:

```clojure
(#{1 2 3} 1) ; => 1
(#{1 2 3} 4) ; => nil

There are more functions in the clojure.sets namespace.

Useful formsλ︎

Logic constructs in clojure are just macros, and look like everything else

(if false "a" "b") ; => "b"
(if false "a") ; => nil

Use let to create temporary bindings

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

Group statements together with do

(do
  (print "Hello")
  "World") ; => "World" (prints "Hello")

Functions have an implicit do

(defn print-and-say-hello [name]
  (print "Saying hello to " name)
  (str "Hello " name))
(print-and-say-hello "Jeff") ;=> "Hello Jeff" (prints "Saying hello to Jeff")

So does let

(let [name "Urkel"]
  (print "Saying hello to " name)
  (str "Hello " name)) ; => "Hello Urkel" (prints "Saying hello to Urkel")

Namespaces and Librariesλ︎

Namespaces are used to organise code into logical groups. The top of each Clojure file has an ns form that defines the namespace name. The domain part of the namespace name is typically the organisation or community name (e.g. GitHub user/organisation)

(ns domain.namespace-name)

All Practicalli projects have namespace domains of practicalli

(ns practicalli.service-name)

require allows code from one namespace to be accessed from another namespace, either from a the same Clojure project or from a library added to the project classpath.

The :as directive with require is used to specify an alias name, a short-hand for the full library name

Or :refer [function-name var-name] can be used to specify specific functions and data (vars) that are available directly

A required directive is typically added to a namespace form

(ns practicalli.service-name
  (require [clojure.set :as set]))

The functions from clojure.set can be used via the alias name, rather than the fully qualified name, i.e. clojure.set/intersection

(set/intersection #{1 2 3} #{2 3 4}) ; => #{2 3}
(set/difference #{1 2 3} #{2 3 4}) ; => #{1}

:require directive can be used to include multiple library namespaces

(ns test
  (:require
    [clojure.string :as string]
    [clojure.set :as set]))

require can be used by itself, usually within a rich code block

(comment
  (require 'clojure.set :as set))

Javaλ︎

Java has a huge and useful standard library, so you'll want to learn how to get at it.

Use import to load a java package

(import java.util.Date)

Or import from a java package name

(ns test
  (:import
    java.util.Date
    java.util.Calendar))

Use the class name with a "." at the end to make a new instance

(Date.) ; <a date object>

Use . to call methods. Or, use the ".method" shortcut

(. (Date.) getTime) ; <a timestamp>
(.getTime (Date.))  ; exactly the same thing.

Use / to call static methods

(System/currentTimeMillis) ; <a timestamp> (system is always present)

Use doto to make dealing with (mutable) classes more tolerable

(import java.util.Calendar)
(doto (Calendar/getInstance)
  (.set 2000 1 1 0 0 0)
  .getTime) ; => A Date. set to 2000-01-01 00:00:00