CPL 2016, week 10 - Clojure functional core...CPL 2016, week 10 - Clojure functional core Author...

Post on 05-Jul-2020

2 views 0 download

Transcript of CPL 2016, week 10 - Clojure functional core...CPL 2016, week 10 - Clojure functional core Author...

CPL 2016, week 10Clojure functional core

Oleg Batrashev

Institute of Computer Science, Tartu, Estonia

April 11, 2016

Overview

TodayI Clojure language core

Next weeksI Immutable data structuresI Clojure simple state and designI Software transactional memoryI Agents in Clojure

Functional core 3/32 -

Overview

I Declarative programming (DP)I what is declarativeness

I Immutable data structuresI lists, trees

I DP basicsI iterative comutationI recursive computation

I Declarative concurrency (DC)I DP and DC in Haskell and other languages

Functional core 4/32 Declarative programming -

Outline

Functional coreDeclarative programmingClojure immutable code-dataBasics of declarative programming

Functional core 5/32 Declarative programming -

Some quotes

I Recent “Effective Scala” from Twitter developershttp://twitter.github.com/effectivescala/

I Use Futures to manage concurrency.I Futures allow the programmer to express concurrent

computation in a declarative styleI If an immutable collection will do, use it ... reasoning about

them in a concurrent context is simple.I The Java memory model is a subtle beast, but luckily we can

avoid all of these pitfalls by using the declarative style

What is declarative style, future and immutable collection and howare they special?

Functional core 6/32 Declarative programming -

What is declarativeness

An operation with input and output is declarativeI Same input always gives the same output

I independent (of outside state)I stateless (no inside state)I deterministic (“fixed” control-flow)I no side effects (no writing to IO or outside state)

ExamplesI typical algebraic operations (+,-,*,/)I if-then-elseI pure functions

Functional core 7/32 Declarative programming -

Declarativeness is important

I CompositionalI plug declarative component

Declarativeoperation

arguments

results

Rest ofcomputation

I Reasoning is simpleI understand component behaviour aloneI no need to consider external/internal state

Functional core 8/32 Declarative programming -

In Clojure (and other FP)

I variables are immutableI function arguments, loop variables, local (let) variablesI except def, defn creates global mutable variables

I standard data structures are immutable (persistent)I list, vector, map, set

I pure functionsI no side effects: i.e. no IO/shared state read or write

Functional core 9/32 Clojure immutable code-data -

Outline

Functional coreDeclarative programmingClojure immutable code-dataBasics of declarative programming

Functional core 10/32 Clojure immutable code-data -

Running code

I REPL (read-eval-print loop)

$ clojureClojure 1.6.0user=> (+ 4 5)9user=> (println "Hello ")Hello

I Create file with extension ’clj’ and run it with clojureI use def to define global variables

(def x 5)(println "Hello" x)

I avoid them for now, because they are not part of declarativemodel

Functional core 11/32 Clojure immutable code-data -

Lisp syntaxI parentheses () play an important role

I almost always call a function

(fnname arg1 arg2 arg3)

I no infix operators, so even adding 2 values

(+ 2 3)

I try to read this

(+ (/ (* 2 5) 3) (- 1 2))

I quoting – do not execute the function

'(+ 2 3)

I returns linked list of 3 values: + function, integers 2 and 3I + is function value (reference to the value)

I code is data, in the form of linked lists!

Functional core 12/32 Clojure immutable code-data -

Basic typesI booleans, integers, rationals, floats

(println true false 5 (/ 1 4) 0.25)

I characters, strings(println \H "ello")

I keywords are symbolic constants (like in Erlang)(println :keyw1 (type :keyw2)); -> :keyw1 clojure.lang.Keyword

I symbols are names for variables, functions, etcI difficult to catch – compiler tries to substitute for their values

(println sym1); -> CompilerException ... Unable to resolve symbol: sym1

I but they are real

(println 'sym1 (class 'sym1)); -> sym1 clojure.lang.Symbol

Functional core 13/32 Clojure immutable code-data -

Immutable linked lists

I (list 3 4 7) or with quoting '(3 4 7)

I actual representation (cons 3 (cons 4 (cons 7 ())))

I empty list () ends the listI “cons” pair (cons H T) consists of

I head H references one list valueI tail T references the rest of the list

I first/rest – accessing head and tailI destructuring: extract head and tail

(let [[H & T] '(3 4 7)] (println T) )

I prints (4 7)

I changing an element in the list is impossibleI adding head (cons 11 lst), also (conj lst 11)

Functional core 14/32 Clojure immutable code-data -

Appending lists

I Appending a=(1 2 3) b=(6 7) must result in copying the first list(cons 1 (cons 2 (cons 3 b)))

1 2 3 6 7

1 2 3

Append one list to another by just reassigning the tail?No!

I the first list must stay immutableI whoever has reference to it should not see changes

I the second list may be appended to any otherI whoever has reference to it sees no changes

Functional core 15/32 Clojure immutable code-data -

Variables

I single-assignment variables – you can only assign value onceI let construct, x and y are not re-assignable

(let [x 5y 10]

(println (- x y)))

I function arguments (x,y) are not re-assignable

(fn [x y] (* x y))

I def assigns to things called vars, return to them in 2 weeks

(def f (fn [x y] (* x y)))(println (f 5 10))

I they are re-assignable

Functional core 16/32 Clojure immutable code-data -

Static scoping

Variables/parameters are only seen within scopeI let – define local symbols and execute statements

clojure.core/let(let [bindings *] exprs*)

I fn – parameter symbols seen within function scope

clojure.core/fn(fn name? [params *] exprs*)(fn name? ([ params *] exprs*) +)

I for – local symbols seen with ’for’ scope

(for [x (range 10)y (range 5):let [z (+ x y)]:when (< z 10)]

(list x y z))

Functional core 17/32 Clojure immutable code-data -

Defining functions – don’t panic

I fn defines anonymous function

(fn [arg1 arg2 arg3] stm1 stm2 stm3)

I def assigns value to global variable

(def sym val)

I the rest is just syntactic sugar

(defn f1 [arg1 arg2] stm1 stm2 stm3); assigns function to global variable 'f1'

I letfn for local functions

(letfn [(f2 [x y](println :we)(- x y))]

(println (f2 2 3)))

Functional core 18/32 Basics of declarative programming -

Outline

Functional coreDeclarative programmingClojure immutable code-dataBasics of declarative programming

Functional core 19/32 Basics of declarative programming -

Iterative computation (1)

How to iterate over values with single-assignment variable?I with recursionI each (recursive) call creates new variables for the arguments

(defn iteration [X Sum](if (>= X 10)

Sum ; stop and return result(do ; else

(println X)(iteration (inc X) (+ Sum X)))))

(println (iteration 0 0))

Functional core 20/32 Basics of declarative programming -

Iterative computation (2)I On the third iteration

"Result"

"X"

"Sum"

x1 0 s1 0

x2 1 s2 0

x3 2 s3 1

current call

I new values are created on the stack in each callI recursion stops at x10

I old xi and si can safely be used by external code/threads(unlike in imperative PLs)

I Java ’final’ keyword for closures

Functional core 21/32 Basics of declarative programming -

Clojure ’recur’

I avoid stack grow with ’recur’I must be in tail positionI calls current function with tail call optimization (TCO)I JVM does not allow TCO, Clojure has to generate iterative

code instead of recursive calls

(defn iteration [i s](if (>= i 10)

s(do ; else

(println i)(recur (inc i) (+ s i)))))

(println (iteration 0 0))

Functional core 22/32 Basics of declarative programming -

Recursive computation (1)

I Naive implementations are often wastefulI stack grows because of append is not in tail position

(defn append [Ls Ms](if (= Ls ())

Ms(cons (first Ls) (append (rest Ls) Ms))))

(println (append '(1 5 3) '(3 2 3)))

I Naive definitions are often slow

(defn reverse [Xs](if (= Xs ())

'()(append (reverse (rest Xs))

(list (first Xs)) )))(println (reverse '(1 2 3 4)))

Functional core 23/32 Basics of declarative programming -

Recursive computation (2)

Use accumulators and tail recursion

(defn - reverse2_iter [Rs Ys](if (= Ys '())

Rs ; return accumulated result(recur (cons (first Ys) Rs) (rest Ys))))

(defn reverse2 [Xs](reverse2_iter '() Xs)) ; empty accumulator

(println (reverse2 '(1 4 3 2)))

I Rs is an accumulator for the new listI Recursive call is in tail positionI same with reduce

(def P (fn [acc val] (cons val acc )))(reduce P [] [1 2 7])

Functional core 24/32 Basics of declarative programming -

Recursion example( de f l e t t e r T y p e s #{Cha ra c t e r /LOWERCASE_LETTER

Cha rac t e r /UPPERCASE_LETTER})( de fn l e t t e r ? [ ch ] ( l e t t e r T y p e s ( Cha r a c t e r / getType ch ) ) )

( defn− scan−word [ r e a d e r word ]"Read s i n g l e word from the r e a d e r . "( l e t [ ch ( . r ead r e a d e r ) ]

( i f ( l e t t e r ? ch )( r e c u r r e a d e r ( cons ch word ) )( app l y s t r (map char ( r e v e r s e word ) ) ) ) ) )

( defn− scan−words−imp l [ r e a d e r words ]( l e t [ ch ( . r ead r e a d e r ) ] ; r ead next symbol

( i f (= ch −1) ; i s end o f r e a d e r ?words ; f i n i s h and r e t u r n words

( i f ( l e t t e r ? ch ); ; scan the word( r e c u r r e a d e r ( con j words ( scan−word r e a d e r ( l i s t ch ) ) ) ); ; s k i p c h a r a c t e r( r e c u r r e a d e r words ) ) ) ) )

( de fn scan−words [ r e a d e r ]( scan−words−imp l r e a d e r [ ] ))

Functional core 25/32 Basics of declarative programming -

Higher-order programming (1)

Reverse the list (1 2 7)1

(P .)

2

(P .)

7

(P .)() (1) (2 1) (7 2 1)

I accumulator values from R0=() to R3=(7 2 1)I can be also viewed as state transform from S0 to S3I tansformation takes In and X and returns Out

Out ← (P In X)

(P .)In

XOut

Functional core 26/32 Basics of declarative programming -

Higher-order programming (2)

Define the generic function

(defn forAllAcc [Lst P Acc](if (empty? Lst)

Acc(let [[X & Tail] Lst ; split the list

NewAcc (P Acc X)] ; update accum(recur Tail P NewAcc )))) ; proceed with the rest

; use sum as the transform function(println (forAllAcc [1 2 3] + 0)); use prepend as the transform function(let [prepend (fn [a x] (cons x a))]

(println (forAllAcc [1 2 3] prepend ())))

I forAllAcc is actually foldlI state transform view: X may be incoming message

Functional core 27/32 Basics of declarative programming -

Idiomatic functional operations

I filter – filter each element of the list according to the predicateI list + predicate = list

I map – transform each element of the listI list + transform function = list

I fold (reduce) – reduce all elements of the list using givenfunction

I list + reduction function = scalarI [X Y Z] and * as the reduction function (notation is not

Clojure)I (X*Y)*Z for left foldingI X*(Y*Z) for right folding

Functional core 28/32 Basics of declarative programming -

Clojure collections

I vectors: [1 2 3], (vector 1 2 3), (vec ’(1 2 3))I maps: {:key1 "val", :key2 42, 99 "ok"}

(class {:key1 "val"}); -> clojure.lang.PersistentArrayMap

I with constructor: (hash-map :key1 "val")

I sorted map (sorted-map :key1 2)

I sets: #{:val1 "str" 12}

I sorted set ...I seq interface: seq, first, rest, next

Functional core 29/32 Basics of declarative programming -

Sequence library in Clojure

I conj, intoI range, repeat, repeatedly, iterateI take, cycleI interleave, interpose (join)I constructors (list, vector, hash-set, hash-map)

I compare to vec

I filter, take-while, drop-while, split-at, split-withI every?, someI map, reduce, sort, sort-byI for – does map + filter