<<

CPL 2016, week 10 functional core

Oleg Batrashev

Institute of , Tartu, Estonia

April 11, 2016 Overview

Today

I Clojure language core Next weeks

I Immutable data structures

I Clojure simple state and design

I Software transactional memory

I Agents in Clojure Functional core 3/32- Overview

I Declarative programming (DP)

I what is declarativeness

I Immutable data structures

I lists, trees

I DP basics

I iterative comutation I recursive computation

I Declarative concurrency (DC)

I 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

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

I Use Futures to manage concurrency. I Futures allow the programmer to express concurrent computation in a declarative style I 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 how are they special? Functional core 6/32 Declarative programming - What is declarativeness

An operation with input and output is declarative

I 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) Examples

I typical algebraic operations (+,-,*,/)

I if-then-else

I pure functions Functional core 7/32 Declarative programming - Declarativeness is important

I Compositional

I plug declarative component

arguments Declarative Rest of operation computation results

I Reasoning is simple

I understand component behaviour alone I no need to consider external/internal state Functional core 8/32 Declarative programming - In Clojure (and other FP)

I variables are immutable

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

I standard data structures are immutable (persistent)

I list, vector, map, set

I pure functions

I 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

I REPL (read-eval-print loop)

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

I Create file with extension ’clj’ and run it with clojure

I use def to define global variables

(defx 5) (println"Hello"x)

I avoid them for now, because they are not part of declarative model Functional core 11/32 Clojure immutable code-data - Lisp syntax

I 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 3 I + 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 types

I 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, etc

I difficult to catch – 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 (cons3(cons4( 7 ())))

I empty list () ends the list I “cons” pair (consHT) consists of

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

I first/rest – accessing head and tail

I destructuring: extract head and tail (let[[H&T] '(3 4 7)] (printlnT))

I prints (4 7)

I changing an element in the list is impossible

I 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 (cons1(cons2(cons3b))) 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 immutable

I whoever has reference to it should not see changes

I the second list may be appended to any other

I 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 once

I let construct, x and y are not re-assignable

(let[x5 y 10] (println(-xy)))

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

(fn[xy](*xy))

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

(deff(fn[xy](*xy))) (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 scope

I 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 (+xy)] :when (

I fn defines

(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[xy] (println:we) (-xy))] (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?

I with

I each (recursive) call creates new variables for the arguments

(defn iteration[X Sum] (if (>=X 10) Sum ; stop and return result (do ; else (printlnX) (iteration(incX) (+ SumX))))) (println(iteration 0 0)) Functional core 20/32 Basics of declarative programming - Iterative computation (2)

I On the third iteration "Result"

"X" x1 0 s1 0

"Sum" x2 1 s2 0

x3 2 s3 1

current call

I new values are created on the stack in each call

I 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 position I 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[is] (if (>=i 10) s (do ; else (printlni) (recur(inci) (+si)) ))) (println(iteration 0 0)) Functional core 22/32 Basics of declarative programming - Recursive computation (1)

I Naive implementations are often wasteful

I 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 list

I Recursive call is in tail position

I same with reduce

(defP(fn[acc val](cons val acc))) (reduceP [] [1 2 7]) Functional core 24/32 Basics of declarative programming - Recursion example

(def letterTypes #{Character/LOWERCASE_LETTER h a r a c t e r/UPPERCASE_LETTER}) (defn letter?[ch](letterTypes(Character/getType ch)))

(defn − scan−word[reader word] "Read single word from the reader." (let[ch(.read reader)] (if(letter? ch) (recur reader(cons ch word)) (apply str(map char(reverse word))))))

(defn − scan−words−i m p l[reader words] (let[ch(.read reader)] ; read next symbol (if (= ch −1) ; is end of reader? words ; finish and return words

(if(letter? ch) ;; scan the word (recur reader(conj words(scan −word reader(list ch)))) ;; skip character (recur reader words)))))

(defn scan −words[reader] (scan −words−i m p l reader[]) ) 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 .)

I accumulator values from R0=() to R3=(7 2 1)

I can be also viewed as state transform from S0 to S3 I tansformation takes In and X and returns Out Out ← (P In X) X In Out (P .) Functional core 26/32 Basics of declarative programming - Higher-order programming (2)

Define the generic function

(defn forAllAcc[LstP Acc] (if(empty? Lst) Acc (let[[X& Tail] Lst ; split the list NewAcc(P AccX)] ; update accum (recur TailP 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[ax](consxa))] (println(forAllAcc [1 2 3] prepend())))

I forAllAcc is actually foldl

I 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 predicate

I list + predicate = list

I map – transform each element of the list

I list + transform function = list

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

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

I (X*Y)*Z for left folding I 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, into

I range, repeat, repeatedly, iterate

I take, cycle

I interleave, interpose (join)

I constructors (list, vector, hash-set, hash-map)

I compare to vec

I filter, take-while, drop-while, split-at, split-with

I every?, some

I map, reduce, sort, sort-by

I for – does map + filter