Clojure 7-Languages

36
Seven Languages in Seven Months Language 6: Clojure Raymond P. de Lacaze 04/29/13

description

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

Transcript of Clojure 7-Languages

Page 1: Clojure 7-Languages

Seven Languages in Seven Months

Language 6: Clojure

Raymond P. de Lacaze

04/29/13

Page 2: Clojure 7-Languages

Overview

• Part 1: Introduction to ClojureChapter 7 of Seven Languages in Seven Weeks

• Part 2: Advanced Topics- Macros- State Management- Concurrency- Multimethods

Page 3: Clojure 7-Languages

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

Page 4: Clojure 7-Languages

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.

Page 5: Clojure 7-Languages

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

Page 6: Clojure 7-Languages

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

Page 7: Clojure 7-Languages

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/

Page 8: Clojure 7-Languages

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)

Page 9: Clojure 7-Languages

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"

Page 10: Clojure 7-Languages

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

Page 11: Clojure 7-Languages

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)

Page 12: Clojure 7-Languages

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)

Page 13: Clojure 7-Languages

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

Page 14: Clojure 7-Languages

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

Page 15: Clojure 7-Languages

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

Page 16: Clojure 7-Languages

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

Page 17: Clojure 7-Languages

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)

Page 18: Clojure 7-Languages

Recursion

• Clojure supports direct recursion but the JVM does not provide tail-optimization.

• Clojure provides loop & recur to essentially provide tail-optimized recursion

Page 19: Clojure 7-Languages

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

Page 20: Clojure 7-Languages

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

Page 21: Clojure 7-Languages

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

Page 22: Clojure 7-Languages

Welcome to the middle of the presentation

Let’s Take a Break!

Page 23: Clojure 7-Languages

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

Page 24: Clojure 7-Languages

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

Page 25: Clojure 7-Languages

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

Page 26: Clojure 7-Languages

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

Page 27: Clojure 7-Languages

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

Page 28: Clojure 7-Languages

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

Page 29: Clojure 7-Languages

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

Page 30: Clojure 7-Languages

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

Page 31: Clojure 7-Languages

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>!

Page 32: Clojure 7-Languages

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

Page 33: Clojure 7-Languages

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

Page 34: Clojure 7-Languages

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

Page 35: Clojure 7-Languages

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

Page 36: Clojure 7-Languages

Other Topics

• Multimethods• Java Integration• Futures & Promises