Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Functional Programming in Swift

Functional Programming in Swift

More Decks by Chris Eidhof | @chriseidhof

Other Decks in Technology

Transcript

  1. Why? You can keep writing normal OOP code. But some

    things are easier when done in a functional way.
  2. No side-effects func add(x: Int, y: Int) -> Int {

    return x + y + Int(arc4random()) } add(1,2) 1176275535
  3. 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
  4. 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
  5. Our data set let cities : Dictionary<String,Int> = [ "Минск":

    1834200 , "Борисов": 147100 , "Солигорск": 102300 , "Молодечно": 94200 , "Жодино": 61800 ] let names = Array(cities.keys) let populations = Array(cities.values)
  6. 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]
  7. Filter func isMinsk(s: String) -> Bool { return s ==

    "Минск" } names.filter(isMinsk) [Минск]
  8. 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
  9. 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
  10. 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 }
  11. Concatenate func concat(strings: String[]) -> String { var result =

    "" for x in strings { result += x } return result } concat(names) ЖодиноМинскМолодечноСолигорскБорисов
  12. Generics func reduce<A>(initialValue: A, combine: (A,A) -> A, arr: A[])

    -> A { var result = initialValue for i in arr { result = combine(result,i) } return result } reduce("", +, names) ЖодиноМинскМолодечноСолигорскБорисов
  13. Adding line-breaks reduce("", { $0 + "\n" + $1 },

    names) Жодино Минск Молодечно Солигорск Борисов
  14. Making reduce more generic func reduce<A,R>(initialValue: R, combine: (R,A) ->

    R, arr: A[]) -> R { var result = initialValue for i in arr { result = combine(result,i) } return result }
  15. Define map in terms of reduce func map<A,B>(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)
  16. Define filter in terms of reduce func filter<A>(array: A[], f:

    A -> Bool) -> A[] { return reduce([], { (var arr, el) in if (f(el)) { arr += el } return arr }, array) } let isEven = { $0 % 2 == 0 } filter(Array(1..10), isEven) ( 2, 4, 6, 8)
  17. ...

  18. 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
  19. 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]
  20. let citiesAndPopulations = cities.keysAndValues > [ (Жодино, 61800) > ,

    (Минск, 1834200) > , (Молодечно, 94200) > , (Солигорск, 102300) > , (Борисов, 147100)]
  21. Sort by name func qsortWith<A>(var array: A[], compare: (A,A) ->

    Bool) -> A[] { if array.count == 0 { return [] } let pivot = array.removeAtIndex(0) let lesser = array.filter { !compare(pivot,$0) } let greater = array.filter { compare(pivot,$0) } return qsortWith(lesser,compare) + [pivot] + qsortWith(greater,compare) }
  22. Sort by name qsortWith(citiesAndPopulations) { (l,r) in l.0 > r.0

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

    } [(Минск, 1834200), (Борисов, 147100), (Солигорск, 102300), (Молодечно, 94200), (Жодино, 61800)]
  24. ...

  25. Diagrams empty: Diagram func rect(#width: Double, #height: Double) -> Diagram

    func circle(#radius: Double) -> Diagram @infix func ||| (l: Diagram, r: Diagram) -> Diagram
  26. 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
  27. Reduce func hcat(d: Diagram[]) -> Diagram { return reduce(empty, |||,

    d) } example = hcat([blueSquare, cyanCircle, redSquare, greenCircle])
  28. 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 }
  29. Diagrams API Almost all functions are pure: create a diagram,

    combine diagrams, color diagrams. Only the rendering is impure.
  30. ...

  31. Check if qsort gives the same result as sort func

    prop_sort(ls: Int[]) -> Bool { return qsort(ls) == sort(ls) }
  32. Idea Let's generate a lot of random integer arrays, and

    run them through our property. Makes it easier to find mistakes.
  33. Let's build QuickCheck func check<X : Arbitrary>(prop : Array<X> ->

    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 }
  34. 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
  35. Better errors protocol Smaller { func smaller() -> Self? }

    extension Array : Smaller { func smaller() -> Array<T>? { if self.count == 0 { return nil } var copy = self copy.removeAtIndex(0) return copy } }
  36. Better errors func check1<X : Arbitrary>(prop : Array<X> -> 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 }
  37. Let's see the results check1 { (x: Int[]) in qsort(x)

    == x } Property doesn't hold: [6890, 465] false
  38. We're just at the beginning Swift also allows for much

    easier OOP, and we can mix and match whatever we want.
  39. There are a lot more cool things • Enums •

    Pattern matching • Optionals • Applicative Functors and Monads