3. First-class and higher-order functions
let double: (Int) -> Int = { $0 * 2 }
func apply(value: Int, function: (Int) -> Int) -> Int {
return function(value)
}
let result = apply(value: 4, function: double)
// result == 8
4. Some properties
• A function is said to be pure if it produces no side-effects and its
return value only depends on its arguments
• An expression is said to be referentially transparent if it can be
replaced by its value without altering the program’s behavior
6. Some very familiar code
var data: [String]?
// ...
let result = data?.first?.uppercased()
7. Some very familiar code
var data: [String]?
// ...
let result = data?.first?.uppercased()
The operator ?. allows us to declare a workflow that will be executed
in order, and will prematurely stop if a nil value is encountered
8. Let’s try to write the code for ?.
extension Optional {
func ?.<U>(lhs: Wrapped?, rhs: (Wrapped) -> U) -> U? {
switch self {
case .some(let value):
return .some(rhs(value))
case .none:
return nil
}
}
}
Disclaimer : this is a simplified
case for methods with no
arguments
10. Let’s manipulate an Array
let data: [Int] = [0, 1, 2]
let result = data.map { $0 * 2 }.map { $0 * $0 }
11. Let’s manipulate an Array
let data: [Int] = [0, 1, 2]
let result = data.map { $0 * 2 }.map { $0 * $0 }
Same thing here: the function map allows us to declare a workflow
12. A possible implementation for map
extension Array {
func map<U>(_ transform: (Element) -> U) -> [U] {
var result: [U] = []
for e in self {
result.append(transform(e))
}
return result
}
}
14. Let’s compose functions
let double: (Int) -> Int = { x in x * 2 }
let square: (Int) -> Int = { x in x * x }
infix operator • : AdditionPrecedence
func •<T, U, V>(lhs: (U) -> V, rhs: (T) -> U) -> ((T) ->
V) {
return { t in lhs(rhs(t)) }
}
let result = (double • square)(4) // result == 32
Disclaimer : @escaping
attributes have been omitted
15. Let’s compose functions
let double: (Int) -> Int = { x in x * 2 }
let square: (Int) -> Int = { x in x * x }
infix operator • : AdditionPrecedence
func •<T, U, V>(lhs: (U) -> V, rhs: (T) -> U) -> ((T) ->
V) {
return { t in lhs(rhs(t)) }
}
let result = (double • square)(4) // result == 32
16. We have three similar behaviors,
yet backed by very different
implementation
17. What are the common parts?
• They contain value(s) inside a context
• They add new features to existing types
• They provide an interface to transform/map the inner value
18. Monad: intuitive definition
• Wraps a type inside a context
• Provides a mechanism to create a workflow of transforms
21. Writer Monad
• We have an application that does a lot of numerical calculation
• It’s hard to keep track of how values have been computed
• We would like to have a way to store a value along with a log of all
the transformation it went through
22. Writer Monad
struct Logged<T> {
let value: T
let logs: [String]
private init(value: T) {
self.value = value
self.logs = ["initialized with value: (self.value)"]
}
static func just(_ value: T) -> Logged<T> {
return Logged(value: value)
}
}
func >>> <U, V>(lhs: Logged<U>, rhs: (U) -> Logged<V>) -> Logged<V> {
let computation = rhs(lhs.value)
return Logged<V>(value: computation.value, logs: lhs.logs + computation.logs)
}
func square(_ value: Int) -> Logged<Int> {
let result = value * value
return Logged(value: result, log: "(value) was squared, result: (result)")
}
func halve(_ value: Int) -> Logged<Int> {
let result = value / 2
return Logged(value: result, log: "(value) was halved, result: (result)")
}
23. Writer Monad
let computation = .just(4) >>> square >>> halve
print(computation)
// Logged<Int>(value: 8, logs: ["initialized with value:
4", "4 was squared, result: 16", "16 was halved, result:
8"])
24. Reader Monad
• We have a function that require environment variables
• We don’t want to hard code those variables, because it makes
testing impossible
• We would like to have a way to declare the operation we want to
perform, but they would be actually executed only when we provide
the environment variables
25. Reader Monad
struct Reader<E, A> {
let g: (E) -> A
init(g: @escaping (E) -> A) {
self.g = g
}
func apply(_ e: E) -> A {
return g(e)
}
func flatMap<B>(_ f: @escaping (A) -> Reader<E, B>) -> Reader<E, B> {
return Reader<E, B> { e in f(self.g(e)).g(e) }
}
}
func >>> <E, A, B>(a: Reader<E, A>, f: @autoclosure @escaping (A) -> Reader<E, B>) ->
Reader<E, B> {
return a.flatMap(f)
}
26. Reader Monad
struct Environment {
var dbPath: String
}
func delete(_ userName: String) -> Reader<Environment, Void> {
return Reader<Environment, Void> { env in
print("Delete (userName) at DB path: (env.dbPath)")
}
}
let testWorkflow = delete("Thor") >>> delete("Loki")
let productionWorkflow = delete("Odin")
testWorkflow.apply(Environment(dbPath: "path_to_test"))
productionWorkflow.apply(Environment(dbPath: "path_to_prod"))
27. IO Monad
• We like pure functional programs because they have no side-effects
• Unfortunately, programs also need to do I/O, which bears side-
effects by definition
• The IO Monad allows us to encapsulate those side effects, and use
them in a pure functional way
30. Formal definition
struct Monad<T> {
let value: T
static func just(_ value: T) -> Monad<T>
}
func >>> <U, V>(lhs: Monad<U>, rhs: (U) -> Monad<V>) ->
Monad<V>
The following API:
Is a Monad if
let x: T, (.just(x) >>> f) == f(x)
let m: (U) -> Monad(V), (m >>> just) == m
let f, g, h: (T) -> T, f >>> g >>> h == f >>> { x in f(x) >>> h(x)
Associativity
Neutral element