Slide 1

Slide 1 text

Functional Programming in Swift Chris Eidhof - objc.io - June 28, Minsk

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

Apple decides everything, and we will like it. — Charles McCathie Nevile

Slide 4

Slide 4 text

Why? You can keep writing normal OOP code. But some things are easier when done in a functional way.

Slide 5

Slide 5 text

What is Functional Programming?

Slide 6

Slide 6 text

Functions func add(x: Int, y: Int) -> Int { return x + y }

Slide 7

Slide 7 text

No side-effects func add(x: Int, y: Int) -> Int { return x + y + Int(arc4random()) } add(1,2) 1176275535

Slide 8

Slide 8 text

Referential Transparency The same input should always give the same output.

Slide 9

Slide 9 text

Avoid State

Slide 10

Slide 10 text

Purity A pure function has no side-effects: • No use of global variables • No reading of files • No use of functions that are not pure

Slide 11

Slide 11 text

Pure functions are referentially transparent

Slide 12

Slide 12 text

In Practice Most of your code should be pure, only small bits have side-effects.

Slide 13

Slide 13 text

Example Suppose we build a library for describing diagrams: • Describing a diagram: pure • Calculating the layout of a diagram: pure • Executing the actual drawing: side-effect

Slide 14

Slide 14 text

Why? This will make your code a lot easier to change, reuse and test.

Slide 15

Slide 15 text

Functional Programming Basics

Slide 16

Slide 16 text

Our data set let cities : Dictionary = [ "Минск": 1834200 , "Борисов": 147100 , "Солигорск": 102300 , "Молодечно": 94200 , "Жодино": 61800 ] let names = Array(cities.keys) let populations = Array(cities.values)

Slide 17

Slide 17 text

Names [Жодино, Минск, Молодечно, Солигорск, Борисов]

Slide 18

Slide 18 text

Populations [61800, 1834200, 94200, 102300, 147100]

Slide 19

Slide 19 text

Map func addCity(s: String) -> String { return s + "is a city" } names.map(addCity) [Жодиноis a city, Минскis a city, Молодечноis a city, Солигорскis a city, Борисовis a city]

Slide 20

Slide 20 text

Filter func isMinsk(s: String) -> Bool { return s == "Минск" } names.filter(isMinsk) [Минск]

Slide 21

Slide 21 text

Filter, simplified names.filter({ (s: String) -> Bool in return s == "Минск" }) [Минск]

Slide 22

Slide 22 text

Filter, more simplified names.filter({ s in return s == "Минск" }) [Минск]

Slide 23

Slide 23 text

Filter, even more simplified names.filter({ return $0 == "Минск" }) [Минск]

Slide 24

Slide 24 text

Filter, simplest names.filter { $0 == "Минск" } [Минск] populations.filter { $0 > 100000 } [1834200, 102300, 147100]

Slide 25

Slide 25 text

Sum of an array func sum(arr: Int[]) -> Int { var result = 0 for i in arr { result += i } return result } sum(Array(1..10)) 45

Slide 26

Slide 26 text

Product of an array func product(arr: Int[]) -> Int { var result = 1 for i in arr { result *= i } return result } product(Array(1..10)) 362880

Slide 27

Slide 27 text

Reduce func reduce(initialValue: Int, combine: (Int,Int) -> Int, arr: Int[]) -> Int { var result = initialValue for i in arr { result = combine(result,i) } return result }

Slide 28

Slide 28 text

Reduce reduce(0, +, Array(1..10)) 45 reduce(1, *, Array(1..10)) 362880

Slide 29

Slide 29 text

Sum and Product let sum = { reduce(0,+,$0) } let product = { reduce(1,*,$0) }

Slide 30

Slide 30 text

Concatenate func concat(strings: String[]) -> String { var result = "" for x in strings { result += x } return result } concat(names) ЖодиноМинскМолодечноСолигорскБорисов

Slide 32

Slide 32 text

Adding line-breaks reduce("", { $0 + "\n" + $1 }, names) Жодино Минск Молодечно Солигорск Борисов

Slide 33

Slide 33 text

Making reduce more generic func reduce(initialValue: R, combine: (R,A) -> R, arr: A[]) -> R { var result = initialValue for i in arr { result = combine(result,i) } return result }

Slide 34

Slide 34 text

Define map in terms of reduce func map(array: A[], f: A -> B) -> B[] { return reduce([], { (var arr: B[], el: A) in arr += f(el) return arr }, array) } map(Array(1..10), { $0 * 2}) ( 2, 4, 6, 8, 10, 12, 14, 16, 18)

Slide 36

Slide 36 text

...

Slide 37

Slide 37 text

QuickSort func partition(v: Int[], left: Int, right: Int) -> Int { var i = left for j in (left + 1)..(right + 1) { if v[j] < v[left] { i += 1 (v[i], v[j]) = (v[j], v[i]) } } (v[i], v[left]) = (v[left], v[i]) return i } func quicksort(v: Int[], left: Int, right: Int) { if right > left { let pivotIndex = partition(v, left, right) quicksort(v, left, pivotIndex - 1) quicksort(v, pivotIndex + 1, right) } } // Source: https://gist.github.com/fjcaetano/b0c00a889dc2a17efad9

Slide 38

Slide 38 text

Functional QuickSort func qsort(var array: Int[]) -> Int[] { if array.count == 0 { return [] } let pivot = array.removeAtIndex(0) let lesser = array.filter { $0 < pivot } let greater = array.filter { $0 >= pivot } return qsort(lesser) + [pivot] + qsort(greater) } qsort(populations) [61800, 94200, 102300, 147100, 1834200]

Slide 39

Slide 39 text

let citiesAndPopulations = cities.keysAndValues > [ (Жодино, 61800) > , (Минск, 1834200) > , (Молодечно, 94200) > , (Солигорск, 102300) > , (Борисов, 147100)]

Slide 41

Slide 41 text

Sort by name qsortWith(citiesAndPopulations) { (l,r) in l.0 > r.0 } [(Солигорск, 102300), (Молодечно, 94200), (Минск, 1834200), (Жодино, 61800), (Борисов, 147100)]

Slide 42

Slide 42 text

Sort by population qsortWith(citiesAndPopulations) { (l,r) in l.1 > r.1 } [(Минск, 1834200), (Борисов, 147100), (Солигорск, 102300), (Молодечно, 94200), (Жодино, 61800)]

Slide 43

Slide 43 text

...

Slide 44

Slide 44 text

Diagrams empty: Diagram func rect(#width: Double, #height: Double) -> Diagram func circle(#radius: Double) -> Diagram @infix func ||| (l: Diagram, r: Diagram) -> Diagram

Slide 45

Slide 45 text

Example diagram let blueSquare = square(1).fill(NSColor.blueColor()) let redSquare = square(2).fill(NSColor.redColor()) let greenCircle = circle(radius:1).fill(NSColor.greenColor()) let cyanCircle = circle(radius: 1).fill(NSColor.cyanColor()) example = blueSquare ||| cyanCircle ||| redSquare ||| greenCircle

Slide 46

Slide 46 text

Reduce func hcat(d: Diagram[]) -> Diagram { return reduce(empty, |||, d) } example = hcat([blueSquare, cyanCircle, redSquare, greenCircle])

Slide 47

Slide 47 text

Bar diagrams func barGraph(input: (String,Double)[]) -> Diagram { return hcat(normalize(values).map { x in return rect(width: 1, height: 3*x).alignBottom() }) }

Slide 48

Slide 48 text

Adding labels func barGraph(input: (String,Double)[]) -> Diagram { let values : Double[] = input.map { $0.1 } let bars = hcat(normalize(values).map { x in return rect(width: 1, height: 3*x).alignBottom() }) let labels = hcat(input.map { x in return text(width: 1, height: 0.3, text: x.0).alignTop() }) return bars --- labels }

Slide 49

Slide 49 text

Diagrams API Almost all functions are pure: create a diagram, combine diagrams, color diagrams. Only the rendering is impure.

Slide 50

Slide 50 text

...

Slide 51

Slide 51 text

QuickCheck Using functional programming to automate testing

Slide 52

Slide 52 text

Check if qsort gives the same result as sort func prop_sort(ls: Int[]) -> Bool { return qsort(ls) == sort(ls) }

Slide 53

Slide 53 text

Idea Let's generate a lot of random integer arrays, and run them through our property. Makes it easier to find mistakes.

Slide 54

Slide 54 text

Running our checker check(prop_sort) true

Slide 55

Slide 55 text

Let's build QuickCheck func check(prop : Array -> Bool) -> Bool { for _ in 0..numberOfIterations { let randomLength = Int(arc4random() % 50) let array : X[] = Array(0..randomLength).map { _ in return X.arbitrary() } if(!prop(array)) { println("Property doesn't hold: \(array)") return false } } return true }

Slide 56

Slide 56 text

Running our checker check(prop_sort) true

Slide 57

Slide 57 text

Checking our checker check { (x: Int[]) in qsort(x) == x } Property doesn't hold: [459, 9052, 1750, 3572, 3101, 3104, 1675, 148, 4781, 5031, 4769, 6738, 2946, 4396, 3190, 4217, 9975, 5143, 7339, 7180, 2041, 6552, 6780, 3839, 7279, 1060, 375, 1096, 5957, 5484, 2832, 8705, 9202, 2158, 3015, 303, 1911, 9846, 8316, 3300] false

Slide 58

Slide 58 text

Better errors protocol Smaller { func smaller() -> Self? } extension Array : Smaller { func smaller() -> Array? { if self.count == 0 { return nil } var copy = self copy.removeAtIndex(0) return copy } }

Slide 59

Slide 59 text

Better errors func check1(prop : Array -> Bool) -> Bool { for _ in 0..numberOfIterations { let randomLength = Int(arc4random() % 50) let array : X[] = Array(0..randomLength).map { _ in return X.arbitrary() } if(!prop(array)) { let smallerValue = iterateWhile(array, { !prop($0) }) { $0.smaller() } println("Property doesn't hold: \(smallerValue)") return false } } return true }

Slide 60

Slide 60 text

Let's see the results check1 { (x: Int[]) in qsort(x) == x } Property doesn't hold: [6890, 465] false

Slide 61

Slide 61 text

Conclusion

Slide 62

Slide 62 text

This is powerful stuff You can start using this in your Swift code.

Slide 63

Slide 63 text

We're just at the beginning Swift also allows for much easier OOP, and we can mix and match whatever we want.

Slide 64

Slide 64 text

There are a lot more cool things • Enums • Pattern matching • Optionals • Applicative Functors and Monads

Slide 65

Slide 65 text

No content

Slide 66

Slide 66 text

@chriseidhof