Download - Clojure 7-Languages
Seven Languages in Seven Months
Language 6: Clojure
Raymond P. de Lacaze
04/29/13
Overview
• Part 1: Introduction to ClojureChapter 7 of Seven Languages in Seven Weeks
• Part 2: Advanced Topics- Macros- State Management- Concurrency- Multimethods
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
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.
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
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
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/
Calling Basic Functions
• Clojure uses prefix notation(<fn-name><arg1>…<argN>)
• Basic Examples:user=> (+ 1 2 3)6user=> (+ 1 (* 2 3) 4)11user=> (+ "a" "b")java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number (NO_SOURCE_FILE:0)
Strings and Characters• Strings
– Simply enclosed in double-quotes with C-style escapinguser=> (println "This sentence\nspans two lines")This sentencespans two linesnil– 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"
Boolean & Expressions
• Clojure uses the reserved symbol true & falseuser=> (= (str \a) "a")trueuser=> (= 1 2)false
• In Clojure, primitive data types are as much as possible aligned with the underlying JVM primitive data typesuser=> (class true)java.lang.Boolean
• The values false and nil are both false, every other value is true
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)
Constructing and Manipulating Listsuser=> (def my-list '(1 2 3))#'user/my-list
• Use first, last & rest to access components of a listuser=> (first my-list)1user=> (last my-list)3user=> (rest my-list)(2 3)
• Use cons and conj to add elements to beginning a listuser=> (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 listsuser=> (concat my-list my-list)(1 2 3 1 2 3)
Vectors & Sets
• Use square brackets to denote vectorsuser=> (class [1 2 3])clojure.lang.PersistentVector
• Use #curly-brackets to denote setsuser=> (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
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 functionsuser=> (my-map 2)"two”
• Keywords are also functionsuser=> (def my-other-map {:one 1 :two 2 :three 3})#'user/my-other-mapuser=> (:two my-other-map)2
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 55
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
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 withuser=> (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)))2user=> (map (fn [x y](+ x y)) '(1 2 3 4 5) '(5 4 3 2 1))(6 6 6 6 6)
Recursion
• Clojure supports direct recursion but the JVM does not provide tail-optimization.
• Clojure provides loop & recur to essentially provide tail-optimized recursion
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
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))falseuser=> (not-every? odd? '(1 2 3 4 5))True
• Sequence Manipulation– map, filter, apply, for comprehensions, reduce
Lazy Evaluation
• Use range to generate finite Sequencesuser=> (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
Welcome to the middle of the presentation
Let’s Take a Break!
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
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
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)5user=> @my-ref5
• Use ref-set and alter to update a ref within a transaction• Use dosync to specify a transactional atomic operation
Transactional Memory• Bank Accounts in STM
user=> (def account1 (ref 1000))#'user/account1
user=> (def account2 (ref 1500))#'user/account2
user=> (defn transfer [a1 a2 amount] (dosync (alter a1 - amount) (alter a2 + amount)))#'user/transfer
user=> (transfer account1 account2 250)1750
user=> @account1750user=> @account21750
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-atomuser=> (swap! my-atom + 3)8user=> (reset! my-atom 1)1user=> @my-atom1
• Several threads updating a guest list, i.e. independent state
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
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
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
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 printnil
Uh oh! The function unless evaluates <expr> regardless of <test>!
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
Macros (cont.)
• Syntax Quote: `• Unquote: ~• Splicing Unquote: ~@
;; Old Definitionuser=> (defmacro unless [test expr](list 'if test nil expr))#'user/unless
;; New Definitionuser=> (defmacro unless [test expr] `(if ~test nil ~expr))#'user/unless
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 baduser=> (defmacro unless [test expr] `(let [] ~expr (if ~test nil ~expr)))#'user/unless
user=> (unless false (println "foo"))foofoonil
;; This is gooduser=> (defmacro unless [test expr] `(let [result# ~expr] result# (if ~test nil result#))#'user/unless
user=> (unless false (println "foo"))foonil
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
Other Topics
• Multimethods• Java Integration• Futures & Promises