KEMBAR78
Monads in Swift | PDF
Monads
Functional Programming
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
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
Optionals
Some very familiar code
var data: [String]?
// ...
let result = data?.first?.uppercased()
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
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
Arrays
Let’s manipulate an Array
let data: [Int] = [0, 1, 2]
let result = data.map { $0 * 2 }.map { $0 * $0 }
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
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
}
}
Functions
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
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
We have three similar behaviors,
yet backed by very different
implementation
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
Monad: intuitive definition
• Wraps a type inside a context
• Provides a mechanism to create a workflow of transforms
Minimal Monad
struct Monad<T> {
let value: T
// additional data
static func just(_ value: T) -> Monad<T> {
return self.init(value: value)
}
}
infix operator >>> : AdditionPrecedence
func >>> <U, V>(lhs: Monad<U>, rhs: (U) -> Monad<V>) -> Monad<V> {
// specific combination code
}
Some applications
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
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)")
}
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"])
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
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)
}
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"))
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
IO Monad
enum IO<T> {
case value(T)
case error
static func just(_ value: T) -> IO<T> {
return .value(value)
}
}
func wrightLine<T>(_ value: T) -> IO<Void> {
print(value as Any)
return IO<Void>.just(())
}
func getLine(_: Void) -> IO<String> {
switch readLine() {
case let value?:
return .value(value)
case nil:
return .error
}
}
@discardableResult func >>> <U, V>(lhs: IO<U>, rhs: (U) -> IO<V>) -> IO<V> {
switch lhs {
case .error:
return .error
case .value(let lhs):
return rhs(lhs)
}
}
IO Monad
.just(()) >>> getLine
>>> { IO<String>.just($0.uppercased()) }
>>> wrightLine
> Hello world!
// HELLO WORLD!
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
How about mobile apps?
Questions ?
Bibliography
• https://en.wikipedia.org/wiki/Monad_(functional_programming)
• https://www.youtube.com/watch?v=ZhuHCtR3xq8 (Brian Beckman:
Don't fear the Monad)
• https://academy.realm.io/posts/slug-raheel-ahmad-using-monads-
functional-paradigms-in-practice-functors-patterns-swift/ (Using
Monads and Other Functional Paradigms in Practice)
• https://github.com/orakaro/Swift-monad-Maybe-Reader-and-Try

Monads in Swift

  • 1.
  • 2.
  • 3.
    First-class and higher-orderfunctions 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 • Afunction 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
  • 5.
  • 6.
    Some very familiarcode var data: [String]? // ... let result = data?.first?.uppercased()
  • 7.
    Some very familiarcode 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 towrite 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
  • 9.
  • 10.
    Let’s manipulate anArray let data: [Int] = [0, 1, 2] let result = data.map { $0 * 2 }.map { $0 * $0 }
  • 11.
    Let’s manipulate anArray 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 implementationfor map extension Array { func map<U>(_ transform: (Element) -> U) -> [U] { var result: [U] = [] for e in self { result.append(transform(e)) } return result } }
  • 13.
  • 14.
    Let’s compose functions letdouble: (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 letdouble: (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 threesimilar behaviors, yet backed by very different implementation
  • 17.
    What are thecommon 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
  • 19.
    Minimal Monad struct Monad<T>{ let value: T // additional data static func just(_ value: T) -> Monad<T> { return self.init(value: value) } } infix operator >>> : AdditionPrecedence func >>> <U, V>(lhs: Monad<U>, rhs: (U) -> Monad<V>) -> Monad<V> { // specific combination code }
  • 20.
  • 21.
    Writer Monad • Wehave 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 • Wehave 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 • Welike 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
  • 28.
    IO Monad enum IO<T>{ case value(T) case error static func just(_ value: T) -> IO<T> { return .value(value) } } func wrightLine<T>(_ value: T) -> IO<Void> { print(value as Any) return IO<Void>.just(()) } func getLine(_: Void) -> IO<String> { switch readLine() { case let value?: return .value(value) case nil: return .error } } @discardableResult func >>> <U, V>(lhs: IO<U>, rhs: (U) -> IO<V>) -> IO<V> { switch lhs { case .error: return .error case .value(let lhs): return rhs(lhs) } }
  • 29.
    IO Monad .just(()) >>>getLine >>> { IO<String>.just($0.uppercased()) } >>> wrightLine > Hello world! // HELLO WORLD!
  • 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
  • 31.
  • 32.
  • 33.
    Bibliography • https://en.wikipedia.org/wiki/Monad_(functional_programming) • https://www.youtube.com/watch?v=ZhuHCtR3xq8(Brian Beckman: Don't fear the Monad) • https://academy.realm.io/posts/slug-raheel-ahmad-using-monads- functional-paradigms-in-practice-functors-patterns-swift/ (Using Monads and Other Functional Paradigms in Practice) • https://github.com/orakaro/Swift-monad-Maybe-Reader-and-Try