Upload
vincent-pradeilles
View
1.248
Download
0
Embed Size (px)
Citation preview
Monads
Functional Programming
First-class and higher-order functionslet 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 codevar data: [String]?
// ...
let result = data?.first?.uppercased()
Some very familiar codevar 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 Arraylet data: [Int] = [0, 1, 2]
let result = data.map { $0 * 2 }.map { $0 * $0 }
Let’s manipulate an Arraylet 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 mapextension 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 functionslet 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 functionslet 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 Monadstruct 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 Monadstruct 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 Monadlet 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 Monadstruct 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 Monadstruct 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 Monadenum 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 definitionstruct 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) == mlet f, g, h: (T) -> T, f >>> g >>> h == f >>> { x in f(x) >>> h(x)
AssociativityNeutral 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