Apple decides everything, and we will like it.
-- Charles McCathie Nevile
You can keep writing normal OOP code. But some things are easier when done in a functional way.
func add(x: Int, y: Int) -> Int {
  return x + y
}func add(x: Int, y: Int) -> Int {
  return x + y + Int(arc4random())
}
add(1,2)1176275535
The same input should always give the same output.
A pure function has no side-effects:
- No use of global variables
- No reading of files
- No use of functions that are not pure
Most of your code should be pure, only small bits have side-effects.
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
This will make your code a lot easier to change, reuse and test.
let cities : Dictionary<String,Int> = 
   [ "Минск":     1834200 
   , "Борисов":   147100
   , "Солигорск": 102300
   , "Молодечно": 94200
   , "Жодино":    61800
   ]
let names = Array(cities.keys)
let populations = Array(cities.values)[Жодино, Минск, Молодечно, Солигорск, Борисов]
[61800, 1834200, 94200, 102300, 147100]
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]
func isMinsk(s: String) -> Bool {
  return s == "Минск"
}
names.filter(isMinsk)[Минск]
names.filter({ (s: String) -> Bool in
  return s == "Минск"
})[Минск]
names.filter({ s in
  return s == "Минск"
})[Минск]
names.filter({ 
  return $0 == "Минск"
})[Минск]
names.filter { $0 == "Минск" }[Минск]
populations.filter { $0 > 100000 }[1834200, 102300, 147100]
func sum(arr: Int[]) -> Int {
  var result = 0
  for i in arr {
    result += i
  }
  return result
}
sum(Array(1..10))45
func product(arr: Int[]) -> Int {
  var result = 1
  for i in arr {
    result *= i
  }
  return result
}
product(Array(1..10))362880
func reduce(initialValue: Int, 
            combine: (Int,Int) -> Int, 
            arr: Int[]) -> Int {
  var result = initialValue
  for i in arr {
    result = combine(result,i)
  }
  return result
}reduce(0, +, Array(1..10))45
reduce(1, *, Array(1..10))362880
let sum     = { reduce(0,+,$0) }
let product = { reduce(1,*,$0) }func concat(strings: String[]) -> String {
    var result = ""
    for x in strings {
        result += x
    }
    return result
}
concat(names)ЖодиноМинскМолодечноСолигорскБорисов
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)ЖодиноМинскМолодечноСолигорскБорисов
reduce("", { $0 + "\n" + $1 }, names)Жодино
Минск
Молодечно
Солигорск
Борисов
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
}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)
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)
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/b0c00a889dc2a17efad9func 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]
let citiesAndPopulations = cities.keysAndValues> [ (Жодино, 61800)
> , (Минск, 1834200) 
> , (Молодечно, 94200)
> , (Солигорск, 102300)
> , (Борисов, 147100)]
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)
}qsortWith(citiesAndPopulations) { (l,r) in l.0 > r.0 }[(Солигорск, 102300), 
 (Молодечно, 94200),
 (Минск, 1834200),
 (Жодино, 61800),
 (Борисов, 147100)]
qsortWith(citiesAndPopulations) { (l,r) in l.1 > r.1 }[(Минск, 1834200),
 (Борисов, 147100),
 (Солигорск, 102300),
 (Молодечно, 94200),
 (Жодино, 61800)]
empty: Diagram
func rect(#width: Double, #height: Double) -> Diagram
func circle(#radius: Double) -> Diagram
@infix func ||| (l: Diagram, r: Diagram) -> Diagramlet 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 ||| greenCirclefunc hcat(d: Diagram[]) -> Diagram {
  return reduce(empty, |||, d)
}
example = hcat([blueSquare, cyanCircle, redSquare, greenCircle])func barGraph(input: (String,Double)[]) -> Diagram {
    return hcat(normalize(values).map { x in
        return rect(width: 1, height: 3*x).alignBottom()
    })
}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
}Almost all functions are pure: create a diagram, combine diagrams, color diagrams.
Only the rendering is impure.
Using functional programming to automate testing
func prop_sort(ls: Int[]) -> Bool {
    return qsort(ls) == sort(ls)
}Let's generate a lot of random integer arrays, and run them through our property. Makes it easier to find mistakes.
check(prop_sort)true
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
}check(prop_sort)true
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
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
  }
}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
}check1 { (x: Int[]) in
  qsort(x) == x
}Property doesn't hold: [6890, 465]
false
You can start using this in your Swift code.
Swift also allows for much easier OOP, and we can mix and match whatever we want.
- Enums
- Pattern matching
- Optionals
- Applicative Functors and Monads

