Clojure in 15 minutesλ︎
A quick tour of the Clojure syntax and common functions, which is so terse you can read through this page in around 15 minutes and have a basic understanding of the language.
Try the code out in the REPL
Start a Clojure REPL or use a Clojure aware editor connected to a REPL and experiment with these code examples.
Using the REPL provides instant feedback on each expression as they are evaluated, greatly increasing your understanding.
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, useful to separate experimental and established code in a namespace.
Clojure expressionsλ︎
Clojure is mostly written with "expressions", a lists of elements inside parentheses, ()
, separated by space characters.
Clojure evaluates the first element in an expression as a function call. Additional elements in the expression are passed as value arguments to the called function.
Organising Clojureλ︎
Clojure code is organised into one or more namespaces. The namespace represents the directory path and file name that contains the code of the particular namespace.
A company name or community repository name is often used making the namespace unique and easier to share & reuse.
ns form returns nil value
The (ns namespace.,,,)
expression returns a nil
value, as its work is done behind the scenes.
All Clojure functions must return a value and nil
is a value that means 'no value'.
Define a longer namespace
Namespaces use dash, directory and file names use underscore
Clojure uses kebab-case
for names (common in Lisp dialects)
Unfortunately the Java Virtual Machine that hosts Clojure does not support dash, -
, in file and directory names, so an underscore, -
, character is used
String manipulationλ︎
The str
function creates a new string from all the arguments passed
"Hello World"
is returned from evaluating the expression.
clojure.string library for manipulating strings
clojure.string
library functions manipulate values and return string values (other clojure.core functions my return characters as results, e.g. map
)
Math, Truth & prefix notationλ︎
Functions use prefix notation, so you can do math with multiple values very easily
Math in Clojure is very precise, no need for operator precedence rules (as there are no operators)
Nesting forms defined a very precise calculation
6
is returned as the value. Nested expressions are typically read inside out. (* 7 3)
is 21
, giving (- 24 21)
expression resulting in 3
. Finally the expression becomes (* 1 2 3)
, resulting in a value of 6
Maintain precision for calculations using a Ratio type in Clojure
22/7
is returned as the value, rather than a floating point value (double) which may loose some precision due to rounding.
Equalityλ︎
=
function provides a test for equality
true
and false
are Boolean values and can be used literally in Clojure.
Predicatesλ︎
A predicate is a function that returns a boolean true
or false
value and by convention the function name ends in ?
, e.g. true?
, false?
, seq?
, even?
, uuid?
.
and
& or
functions can be used to chain the results of predicates together for more interesting conditional tests.
One of the predicates or values is true
Truthy and Falsy values in Clojure
false
boolean value and nil
value are considered false in Clojure.
All other values are consider true.
Clojure Standard Library Predicate Functions
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.
Only lists are sequences
Sequences are an interface for logical lists, which can be lazy. "Lazy" means that a sequence of values are not evaluated until accessed.
A lazy sequence enables the use of large or even an infinite series, like so:
Lazy sequences
Use cons to add an item to the beginning of a list or vector
Use conj to add an item relative to the type of collection, to the beginning of a list or the end of a vector
Use concat to add lists or vectors together
Use filter, map to interact with collections
Use reduce to reduce them
Reduce can take an initial-value argument too
Equivalent of (conj (conj (conj [] 3) 2) 1)
Annonymous 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.
Wrap a (fn ,,,)
form in parens to call it and return the result.
Normally the anonymous function is used inline with other code
Make the anonymous function reusable by binding it to a shared name (var
) using def
.
The var
name bound to the function can now be called anywhere in the namespace.
As
def
creates avar
(variable) name, the developer can changed the expression the name is bound to and re-evaluated to use the changed behaviour.
NOTE:
hello-world
is a name and not a function call, so parentheses are not required.
Shared Functionsλ︎
It is more common to use the defn
macro to define a function. This is the same as defining the fn
function and the def
name within the same expression
Define a function with defn macro
#'user/hello-world
is the value returned from evaluating the expression, showing the fully qualified name of the function. Note: the fully qualified name will be different when defined in a differnt namespace than user
.
A
defn
function has the scope of the current namespace, so can be called anywhere in the namespace or in a namepace that has usedrequire
to include this namespace.
The []
vector is used to define the argument names for the function. There can be zero or more arguments.
The correct number of arguments must be used when calling a function, or an error will be returned.
Pass a hash-map as an argument
Simplify the design of a function signature by passing all arguments as a hash-map.
Associative Destructuring can be used to automatically create local variables from the desired keys contained in the map, giving access to the value of each key.Clojure supports multi-variadic functions, allowing one function definition to respond to a function call with different number of arguments. This provides a simple form of polymorphism based on the number of arguments.
-
Call
hello-polly
with one argument -
Call
hello-polly
with zero arguments
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λ︎
Keywords are like strings with some efficiency bonuses
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
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
Use assoc to add new keys to hash-maps
But remember, clojure types are immutable!
Use dissoc to remove keys
Setsλ︎
Add a member with conj
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
Use let to create temporary bindings
Group statements together with do
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)
All Practicalli projects have namespace domains of practicalli
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
The functions from clojure.set can be used via the alias name, rather than the fully qualified name, i.e. clojure.set/intersection
:require
directive can be used to include multiple library namespaces
require
can be used by itself, usually within a rich code block
Strong Dynamic 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.
Clojure is a hosted language and uses the type system of the platform it runs upon. For example, Clojure uses Java object types for booleans, strings and numbers under the covers.
Use class
or type
function to inspect the type of some code in Clojure.
(type 1) ; Integer literals are java.lang.Long by default
(type 1.); Float literals are java.lang.Double
(type ""); Strings always double-quoted, and are java.lang.String
(type false) ; Booleans are java.lang.Boolean
(type nil); The "null" value is called nil
Vectors and Lists are java classes too!
Type hints
Type hints can be used to avoid reflection look-ups where performace critical issues have been identified. Type hints are not required in general. Clojure Type Hints
Java Interopλ︎
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
Or import from a java package name
Use the class name with a "." at the end to make a new instance
Use .
to call methods. Or, use the ".method" shortcut
Use / to call static methods
Use doto to make dealing with (mutable) classes more tolerable