These are my slides from a mini Clojure tutorial presented at the "7 Languages in 7 Months" meetup group. The first part of the presentation faithfully presents material from Bruce Tate book, and the second part covers the more advanced topics of state management and macros
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
Clojure 7-Languages
1. Seven Languages in Seven Months
Language 6: Clojure
Raymond P. de Lacaze
04/29/13
2. Overview
• Part 1: Introduction to Clojure
Chapter 7 of Seven Languages in Seven Weeks
• Part 2: Advanced Topics
- Macros
- State Management
- Concurrency
- Multimethods
3. Clojure Introduction
• Clojure is a functional language
• Clojure is a dialect of Lisp
• Clojure runs on the JVM
• Clojure is a strong dynamically typed language
• Clojure uses parenthesized prefix notation
• Clojure encourages immutable objects
• Clojure provides Software Transactional Memory
• Clojure was invented by Rich Hickey
4. Functional Programming
• In the functional language paradigm,
programs are collections of functions.
• Functions are defined as a composition of
other functions
• All functions return values even those that are
used purely for their side effects like print
statements
• There is no distinction between statements
and function calls.
5. Higher Order Functions
• These are functions that take other functions
as arguments
• This is a very powerful data & control
abstraction paradigm
• One of the most common uses is to map a
function over a collection of values, essentially
invoking the function on each member of the
collection
6. Homoiconicity
• This is basically the idea of treating data and code as the
same
• Data structures in the language are used to represent
programs as well as data
• Lisp(s) arguably provides the most elegant and
indistinguishable implementation of this idea
• The source code of a Clojure function is actually a data
structure of type list
• This means that programs can construct and manipulate
code
• We will see this in action when we talk about Macros
• The only way to do this in most languages is by
manipulating programs as strings of characters
7. Installing Clojure
• Official Clojure Site
– http://clojure.org/
• Windows: The Clojure Box
– Easiest way to get up and running
– Downloads, installs & configures Emacs & Clojure
– https://github.com/devinus/clojure-box
• Mac OS X
– http://www.waratuman.com/2010/02/18/setting-up-clojure/
• Leiningen
– Manage Java/Clojure dependencies
– Manage Clojure projects
– http://leiningen.org/
9. Strings and Characters
• Strings
– Simply enclosed in double-quotes with C-style escaping
user=> (println "This sentencenspans two lines")
This sentence
spans two lines
nil
– Use str to convert and concatenate things to strings
– If the underlying class of thing is a java class, then this will simply invoke Java
toString method
• Characters
– These are simply expressed by prefixing the character with a backslash
user=> (str c h a r a c t e r s)
"characters"
10. Boolean & Expressions
• Clojure uses the reserved symbol true & false
user=> (= (str a) "a")
true
user=> (= 1 2)
false
• In Clojure, primitive data types are as much as possible
aligned with the underlying JVM primitive data types
user=> (class true)
java.lang.Boolean
• The values false and nil are both false, every other value is true
11. Lists
• Lists are arguably the most used data structure in Lisp
which is actually an acronym of sorts for “List
Processing”
• Clojure uses parenthesized expressions to denote lists
• Clojure allows optional commas in lists to improve
readability and in that context commas=whitespace
• The list function is the basic list constructor
user=> (list 1 2 3)
(1 2 3)
user=> '(1 , 2 , 3)
(1 2 3)
12. Constructing and Manipulating Lists
user=> (def my-list '(1 2 3))
#'user/my-list
• Use first, last & rest to access components of a list
user=> (first my-list)
1
user=> (last my-list)
3
user=> (rest my-list)
(2 3)
• Use cons and conj to add elements to beginning a list
user=> (cons 4 my-list)
(4 1 2 3)
user=> (conj my-list 4)
(4 1 2 3)
• Use concat to append any number of lists together or into for 2 lists
user=> (concat my-list my-list)
(1 2 3 1 2 3)
13. Vectors & Sets
• Use square brackets to denote vectors
user=> (class [1 2 3])
clojure.lang.PersistentVector
• Use #curly-brackets to denote sets
user=> (class #{:peter :paul :mary})
clojure.lang.PersistentHashSet
• Use sorted-set to create a sorted set.
user=> (class (sorted-set #{:peter :paul :mary}))
clojure.lang.PersistentTreeSet
14. Maps
• Maps are key-value pairs
• Use curly-brackets to denote maps
user=> (def my-map {1 "one", 2 "two", 3 "three"})
#'user/my-map
• Maps are also functions
user=> (my-map 2)
"two”
• Keywords are also functions
user=> (def my-other-map {:one 1 :two 2 :three 3})
#'user/my-other-map
user=> (:two my-other-map)
2
15. Defining Functions
• Use defn to define functions
user=> (defn verbose-sum [x y]
(let [sum (+ x y)]
(println (str "The sum of " x " and " y " is " sum))
sum)
#'user/verbose-sum
user=> (verbose-sum 2 3)
The sum of 2 and 3 is 5
5
16. Destructuring Bindings
• Destructuring supported in both function parameters and let statements
user=> (def board [[:x :o :x][:o :x :o][:o :x :o]])
#'user/board
user=> (defn center [[[a1 a2 a3][b1 b2 b3][c1 c2 c3]]] b2)
#'user/center
user=> (center board)
:x
user=> (defn center [[_ [_ c _] _]] c)
#'user/center
user=> (center board)
:x
17. Anonymous Functions
• Use fn or # to specify an anonymous function
user=> (map (fn [x] (* 2 x)) '(1 2 3 4 5))
(2 4 6 8 10)
user=> (map #(* 2 %) '(1 2 3 4 5))
(2 4 6 8 10)
• map, apply & filter
– These are three higher order functions that you may frequently use
anonymous functions with
user=> (filter #(< % 4) '(1 2 3 4 5))
(1 2 3)
user=> (apply max (map #(mod % 3) '(1 2 3 4 5 6 7 8 9 10)))
2
user=> (map (fn [x y](+ x y)) '(1 2 3 4 5) '(5 4 3 2 1))
(6 6 6 6 6)
18. Recursion
• Clojure supports direct recursion but the JVM
does not provide tail-optimization.
• Clojure provides loop & recur to essentially
provide tail-optimized recursion
19. Sequences
• A sequence Implementation-independent
abstraction over collection types.
• If your data-type supports first, rest and cons
you can wrap it in a sequence.
• Lists, Vectors, Sets and Maps are all Sequences
20. Common Sequence Operations
• These are all higher order functions
• Existential Functions (Tests)
– every?, some, not-every? and not-any?
user=> (every? odd? '(1 2 3 4 5))
false
user=> (not-every? odd? '(1 2 3 4 5))
True
• Sequence Manipulation
– map, filter, apply, for comprehensions, reduce
21. Lazy Evaluation
• Use range to generate finite Sequences
user=> (range 1 10)
(1 2 3 4 5 6 7 8 9)
• Infinite Sequences
– take: Returns the first n elements of a sequence
– repeat: Creates an infinite sequence of 1 repeating element
– drop: Drops the first n elements from a sequence
– cycle: Repeats elements in a sequence
– interpose: interpose one element between elements
– interleave: interleaves two infinite sequence
– iterate: applies a function accumlatively to successive elements
22. Welcome to the middle
of the presentation
Let’s Take a Break!
23. State Management
• Clojure advocates eliminating state
• Cleanliness, Protection & Parallelization
• But, the real world has state
– e.g. bank accounts
• Problems with locks
– Hard to manage correctly
– Overuse of locks tends to limit concurrency
24. State and Identity
• Every thing consists of state and identity
• State is immutable
• Identity links states over time
• State can be any Clojure data type
• Identifies are modeled using reference types
– Refs: Used for synchronous, coordinated state
– Agents: Used for asynchronous, independent state
– Atoms: Used for synchronous, independent state
25. References
• Use ref to create a ref and deref to dereference it
user=> (def my-ref (ref 5))
#'user/my-ref
user=> (deref my-ref)
5
user=> @my-ref
5
• Use ref-set and alter to update a ref within a transaction
• Use dosync to specify a transactional atomic operation
27. Atoms
• Use atom to create an atom type reference
• Use de-ref and @ to deference it
• Use swap! and reset! to update it
user=> (def my-atom (atom 5))
#'user/my-atom
user=> (swap! my-atom + 3)
8
user=> (reset! my-atom 1)
1
user=> @my-atom
1
• Several threads updating a guest list, i.e. independent state
28. Agents
• Use agent to create an agent type ref
• Use send to request an update
• Update semantics
– Actions to same agent applied serially
– Multiple actions to same agent preserve order
– Action-generating actions are not called until the
action completes
– Actions within STM transactions are not called until
the transaction completes
• Concurrency functions: pmap, pvalues & pcalls
29. State Management Summary
• Use refs for coordinated synchronous updates
• Use atoms for independent synchronous updates
• Use agents for asynchronous updates and
concurrency
• Vars maintain state within a thread
• Validator functions help with data integrity
• Watches trigger events based on identity values
30. Macros
• Macros expand into code and a macro definition
specifies the code expansion.
• Think of macros as a mechanism that allows you to add
features to a language rather than within a language.
• Macros don’t evaluate their arguments
• Macros are extremely powerful and should be used
with care
• Use defmacro to define macros
• Use macroexpand to debug macros
• Use `, ~ and ~@ to facilitate code generation
• Use “auto-gensym” or gensym to define local vars
31. Why Macros?
We want add a new language feature:
(unless <test> <expr>)
Using a function we could do:
user=> (defn unless [test expr]
(if test nil expr))
#'user/unless
user=> (unless false (println "This should print."))
This should print.
nil
user=> (unless true (println "This should not print"))
This should not print
nil
Uh oh! The function unless evaluates <expr> regardless of <test>!
32. A Macro Example
user=> (defmacro unless [test expr](list 'if test nil expr))
#'user/unless
user=> (unless false (println "This should print."))
This should print.
nil
;; This now behaves correctly
user=> (unless true (println "This should not print"))
nil
34. Side Effect Safety
• When you define a macro, it is unsafe to evaluate the arguments more
than once in the event that they have side effects.
• Clojure provides auto-gensym and gensym to allow you to define local
variables in safe way and also avoid incorrectly repeated side-effects
;; This is bad
user=> (defmacro unless [test expr]
`(let []
~expr
(if ~test nil ~expr)))
#'user/unless
user=> (unless false (println "foo"))
foo
foo
nil
;; This is good
user=> (defmacro unless [test expr]
`(let [result# ~expr]
result#
(if ~test nil result#))
#'user/unless
user=> (unless false (println "foo"))
foo
nil
35. Datatypes & Protocols
• Roughly analogous to classes & interfaces Java
but more flexible
• Protocols are sets of methods
• Protocols are defined with defprotocol
• Datatypes are named record types, with a set
of named fields, that can implement records
and interfaces.
• Datatypes are defined with defrecord