CPL 2016, week 10 Clojure functional core Oleg Batrashev Institute of Computer Science, Tartu, Estonia April 11, 2016
Overview Today Clojure language core Next weeks Immutable data structures Clojure simple state and design Software transactional memory Agents in Clojure
Functional core 3/32 - Overview Declarative programming (DP) what is declarativeness Immutable data structures lists, trees DP basics iterative comutation recursive computation Declarative concurrency (DC) DP and DC in Haskell and other languages
Functional core 4/32 Declarative programming - Outline Functional core Declarative programming Clojure immutable code-data Basics of declarative programming
Functional core 5/32 Declarative programming - Some quotes Recent Effective Scala from Twitter developers http://twitter.github.com/effectivescala/ Use Futures to manage concurrency. Futures allow the programmer to express concurrent computation in a declarative style If an immutable collection will do, use it... reasoning about them in a concurrent context is simple. 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 how are they special?
Functional core 6/32 Declarative programming - What is declarativeness An operation with input and output is declarative Same input always gives the same output independent (of outside state) stateless (no inside state) deterministic ( fixed control-flow) no side effects (no writing to IO or outside state) Examples typical algebraic operations (+,-,*,/) if-then-else pure functions
Functional core 7/32 Declarative programming - Declarativeness is important Compositional plug declarative component Declarative operation arguments results Rest of computation Reasoning is simple understand component behaviour alone no need to consider external/internal state
Functional core 8/32 Declarative programming - In Clojure (and other FP) variables are immutable function arguments, loop variables, local (let) variables except def, defn creates global mutable variables standard data structures are immutable (persistent) list, vector, map, set pure functions no side effects: i.e. no IO/shared state read or write
Functional core 9/32 Clojure immutable code-data - Outline Functional core Declarative programming Clojure immutable code-data Basics of declarative programming
Functional core 10/32 Clojure immutable code-data - Running code REPL (read-eval-print loop) $ clojure Clojure 1.6.0 user => (+ 4 5) 9 user => ( println " Hello ") Hello Create file with extension clj and run it with clojure use def to define global variables ( def x 5) ( println " Hello " x) avoid them for now, because they are not part of declarative model
Functional core 11/32 Clojure immutable code-data - Lisp syntax parentheses () play an important role almost always call a function ( fnname arg1 arg2 arg3 ) no infix operators, so even adding 2 values (+ 2 3) try to read this (+ (/ (* 2 5) 3) (- 1 2)) quoting do not execute the function '(+ 2 3) returns linked list of 3 values: + function, integers 2 and 3 + is function value (reference to the value) code is data, in the form of linked lists!
Functional core 12/32 Clojure immutable code-data - Basic types booleans, integers, rationals, floats ( println true false 5 (/ 1 4) 0.25) characters, strings ( println \H " ello ") keywords are symbolic constants (like in Erlang) ( println : keyw1 ( type : keyw2 )) ; -> : keyw1 clojure. lang. Keyword symbols are names for variables, functions, etc difficult to catch compiler tries to substitute for their values ( println sym1 ) ; -> CompilerException... Unable to resolve symbol : sym1 but they are real ( println 'sym1 ( class 'sym1 )) ; -> sym1 clojure. lang. Symbol
Functional core 13/32 Clojure immutable code-data - Immutable linked lists (list 3 4 7) or with quoting '(3 4 7) actual representation (cons 3 (cons 4 (cons 7 ()))) empty list () ends the list cons pair (cons H T) consists of head H references one list value tail T references the rest of the list first/rest accessing head and tail destructuring: extract head and tail (let [[H & T] '(3 4 7)] (println T) ) prints (4 7) changing an element in the list is impossible adding head (cons 11 lst), also (conj lst 11)
Functional core 14/32 Clojure immutable code-data - Appending lists 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! the first list must stay immutable whoever has reference to it should not see changes the second list may be appended to any other whoever has reference to it sees no changes
Functional core 15/32 Clojure immutable code-data - Variables single-assignment variables you can only assign value once let construct, x and y are not re-assignable ( let [x 5 y 10] ( println (- x y ))) function arguments (x,y) are not re-assignable (fn [x y] (* x y)) def assigns to things called vars, return to them in 2 weeks ( def f (fn [x y] (* x y ))) ( println (f 5 10)) they are re-assignable
Functional core 16/32 Clojure immutable code-data - Static scoping Variables/parameters are only seen within scope let define local symbols and execute statements clojure. core / let ( let [ bindings *] exprs *) fn parameter symbols seen within function scope clojure. core /fn (fn name? [ params *] exprs *) (fn name? ([ params *] exprs *) +) 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 fn defines anonymous function ( fn [ arg1 arg2 arg3 ] stm1 stm2 stm3 ) def assigns value to global variable ( def sym val ) the rest is just syntactic sugar ( defn f1 [ arg1 arg2 ] stm1 stm2 stm3 ) ; assigns function to global variable ' f1 ' 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 core Declarative programming Clojure immutable code-data Basics of declarative programming
Functional core 19/32 Basics of declarative programming - Iterative computation (1) How to iterate over values with single-assignment variable? with recursion 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) On the third iteration "Result" "X" "Sum" x 1 0 s 1 0 x 2 1 s 2 0 x 3 2 s 3 1 current call new values are created on the stack in each call recursion stops at x 10 old x i and s i can safely be used by external code/threads (unlike in imperative PLs) Java final keyword for closures
Functional core 21/32 Basics of declarative programming - Clojure recur avoid stack grow with recur must be in tail position calls current function with tail call optimization (TCO) 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) Naive implementations are often wasteful 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))) 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))) Rs is an accumulator for the new list Recursive call is in tail position 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 ( d e f l e t t e r T y p e s #{C h a r a c t e r /LOWERCASE_LETTER C h a r a c t e r /UPPERCASE_LETTER}) ( d e f n l e t t e r? [ ch ] ( l e t t e r T y p e s ( C h a 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 t h e r e a d e r. " ( l e t [ ch (. r e a d 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 ) ) ( a p p l y s t r (map c h a r ( r e v e r s e word ) ) ) ) ) ) ( defn scan words i m p l [ r e a d e r words ] ( l e t [ ch (. r e a d r e a d e r ) ] ; r e a d n e x t 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 t h e word ( r e c u r r e a d e r ( c o n 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 ) ) ) ) ) ( d e f n scan words [ r e a d e r ] ( scan words i m p 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 2 7 () (1) (2 1) (7 2 1) (P.) (P.) (P.) accumulator values from R0=() to R3=(7 2 1) can be also viewed as state transform from S0 to S 3 tansformation takes In and X and returns Out Out (P In X) In X (P.) Out
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 ()))) forallacc is actually foldl state transform view: X may be incoming message
Functional core 27/32 Basics of declarative programming - Idiomatic functional operations filter filter each element of the list according to the predicate list + predicate = list map transform each element of the list list + transform function = list fold (reduce) reduce all elements of the list using given function list + reduction function = scalar [X Y Z] and * as the reduction function (notation is not Clojure) (X*Y)*Z for left folding X*(Y*Z) for right folding
Functional core 28/32 Basics of declarative programming - Clojure collections vectors: [1 2 3], (vector 1 2 3), (vec (1 2 3)) maps: {:key1 "val", :key2 42, 99 "ok"} ( class {: key1 " val "}) ; -> clojure. lang. PersistentArrayMap with constructor: (hash-map :key1 "val") sorted map (sorted-map :key1 2) sets: #{:val1 "str" 12} sorted set... seq interface: seq, first, rest, next
Functional core 29/32 Basics of declarative programming - Sequence library in Clojure conj, into range, repeat, repeatedly, iterate take, cycle interleave, interpose (join) constructors (list, vector, hash-set, hash-map) compare to vec filter, take-while, drop-while, split-at, split-with every?, some map, reduce, sort, sort-by for does map + filter