monads in swift

33
Monads

Upload: vincent-pradeilles

Post on 22-Jan-2018

1.244 views

Category:

Software


0 download

TRANSCRIPT

Page 1: Monads in Swift

Monads

Page 2: Monads in Swift

Functional Programming

Page 3: Monads in Swift

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

Page 4: Monads in Swift

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

Page 5: Monads in Swift

Optionals

Page 6: Monads in Swift

Some very familiar codevar data: [String]?

// ...

let result = data?.first?.uppercased()

Page 7: Monads in Swift

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

Page 8: Monads in Swift

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

Page 9: Monads in Swift

Arrays

Page 10: Monads in Swift

Let’s manipulate an Arraylet data: [Int] = [0, 1, 2]

let result = data.map { $0 * 2 }.map { $0 * $0 }

Page 11: Monads in Swift

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

Page 12: Monads in Swift

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 } }

Page 13: Monads in Swift

Functions

Page 14: Monads in Swift

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

Page 15: Monads in Swift

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

Page 16: Monads in Swift

We have three similar behaviors, yet backed by very different

implementation

Page 17: Monads in Swift

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

Page 18: Monads in Swift

Monad: intuitive definition• Wraps a type inside a context

• Provides a mechanism to create a workflow of transforms

Page 19: Monads in Swift

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 }

Page 20: Monads in Swift

Some applications

Page 21: Monads in Swift

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

Page 22: Monads in Swift

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)") }

Page 23: Monads in Swift

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"])

Page 24: Monads in Swift

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

Page 25: Monads in Swift

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) }

Page 26: Monads in Swift

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"))

Page 27: Monads in Swift

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

Page 28: Monads in Swift

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) } }

Page 29: Monads in Swift

IO Monad.just(()) >>> getLine >>> { IO<String>.just($0.uppercased()) } >>> wrightLine

> Hello world!

// HELLO WORLD!

Page 30: Monads in Swift

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

Page 31: Monads in Swift

How about mobile apps?

Page 32: Monads in Swift

Questions ?

Page 33: Monads in Swift

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