Share
Embed
Monads are Not Monsters - UIKonf 2015
UIKonf 2015 talk Monads are not Monsters.
Junior B.
May 18, 2015
Transcript
42 slides
-
What is Functional Programming? (In a nutshell) • It's a
style not only a paradigm • Programming with first class functions • Modularization of the software • Avoid side effects (where possible) • Immutability (where possible) 2 -
Crockford's law In addition to it begin useful, it is
also cursed and the curse of the monad is that once you get the epiphany, once you understand - "oh that's what it is" - you lose the ability to explain it to anybody. — Doug Crockford 4 -
It's about Compositionality and Effects In functional programming we can
compose things, how can we use it? 5 -
Example (Initial) func refreshData() { var error : NSErrorPointer =
nil var response: NSURLResponse? let contentRequest = NSURLRequest(URL: NSURL(string: "https://lobste.rs/hottest.json")!) let data = NSURLConnection.sendSynchronousRequest(contentRequest, returningResponse: &response, error: error) if let d = data { self.list = NSJSONSerialization.JSONObjectWithData(d, options: nil, error: error) as! Array<Dictionary<String, AnyObject>>? if let l = self.list { self.title = "Lobsters (Hottest \(l.count))" self.tableView.reloadData() } } } 6 -
Let's improve it! - Version 1 (1/2) func retrieveData(stringURL: String)
-> NSData? { var error : NSErrorPointer = nil var response: NSURLResponse? let contentRequest = NSURLRequest(URL: NSURL(string: stringURL)!) return NSURLConnection.sendSynchronousRequest(contentRequest, returningResponse: &response, error: error) } func deserializeData(data: NSData) -> Array<Dictionary<String, AnyObject>>? { var error : NSErrorPointer = nil return NSJSONSerialization.JSONObjectWithData(data, options: nil, error: error) as! Array<Dictionary<String, AnyObject>>? } 8 -
Version 1 (2/2) func updateUI(items: Array<Dictionary<String, AnyObject>>) { //Side Effects
self.list = items self.title = "Lobsters (Hottest \(items.count))" self.tableView.reloadData() } // Very compact, but order is reversed func refreshData() { let url = "https://lobste.rs/hottest.json" updateUI(deserializeData(retrieveData(url)!)!) } 9 -
Solution Turn Optional into a Monad // Turn the Optional
type into a Monad extension Optional { // Definition of flatMap func flatMap<U>(f: (a: T) -> Optional<U>) -> Optional<U> { switch (self) { case .None: return nil case .Some(let value): return f(a: value) } } } 11 -
...and the operator // Operator definition public func >>= <T,U>(lhs:
Optional<T>, rhs: T -> Optional<U>) -> Optional<U> { return lhs.flatMap(rhs) } 12 -
Crash Free - Version 2 func refreshData() { let url
= "https://lobste.rs/hottest.json" if let result = retrieveData(url) >>= self.deserializeData { updateUI(result) } } 13 -
With Optionals we are crash free, but what about errors?
We need to handle errors... How? 18 -
With a Result type! public enum Result<V> { case Error(NSError)
case Value(Box<V>) // A Box is a workaround to avoid // compilation failure public init(_ e: NSError?, _ v: V) { if let ex = e { self = Result.Error(ex) } else { self = Result.Value(Box(v)) } } // [...] } 19 -
FlatMap for Result // Our FlatMap operator for Result type!
public func >>= <VA, VB>(a: Result<VA>, f: VA -> Result<VB>) -> Result<VB> { switch a { case let .Error(l): return .Error(l) case let .Value(r): return f(r.value) } } 20 -
What is the result? (Version 3) func refreshData() { let
url = "https://lobste.rs/hottest.json" (retrieveData(url) >>= deserializeData) >>- updateUI } 21 -
Version 3 (1/2) func retrieveData(stringURL: String) -> Result<NSData> { var
error : NSError?; var response: NSURLResponse? let contentRequest = NSURLRequest(URL: NSURL(string: stringURL)!) if let data = NSURLConnection.sendSynchronousRequest(contentRequest, returningResponse: &response, error: &error) { return Result(data) } return Result(error!) } 22 -
Version 3 (2/2) func deserializeData(data: NSData) -> Result<Array<Dictionary<String, AnyObject>>> {
var error : NSError? let items = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &error) as? Array<Dictionary<String, AnyObject>> if let i = items { return Result(i) } return Result(error!) } 23 -
We need to make our code asynchronous: I PROMISE you,
FUTURES are here to help. 25 -
What is a Future? A Future is an object holding
a value which may become available at some point and: • If the runnable block has not yet completed, we say that the Future is not completed. • If the runnable block has completed with a value or an exception, we say that the Future is completed. 26 -
What is a Promise? A Promise is an "extension" of
Future, with the following differences: • A Promise "can" return a Future (not the opposite) • A Promise can or cannot be fulfilled • A Promise is read/write 27 -
How Future Works? A future needs: • An execution context
to run (Queue) • A task to complete 28 -
Eventually • An 'on success' block to run • An
'on error' block to run • An 'on complete' block run 29 -
Future public class Future<T> { public typealias Runnable = ()
-> Result<T> typealias OnSuccessCallback = (T) -> () public typealias OnFailureCallback = (NSError) -> () typealias OnCompleCallback = (result: Result<T>) -> () // wrapper around dispatch_queue_t let executor: ExecutionContext var result: Result<T>? = nil { didSet { // perform onSuccess, onComplete, onFailure self.performCallbacks() } } let task: Runnable? //[...] } 30 -
Chaining public func then<U>(_ executor: ExecutionContext = ExecutionContext.defaultContext , _
task: (value: T) -> Result<U>) -> Future<U> { let future = Future<U>() self.onComplete(){ result in switch result { case .Error(let e): synchronized(future) { future.result = Result<U>(e) } case .Value(let val): future.execute(executor){ return task(value: result.value!) } } } return future } 31 -
Promise public class Promise<T> : Future<T> { // Fulfill the
promise successfully public func success (v: T) { //... } // Process with failure public func failure (e: NSError) { //... } } 32 -
Final Version func refreshData() { let url = "https://lobste.rs/hottest.json" ///
this is a convenience global function to create a Future future { return self.retrieveData(url) }.then { data in return self.deserializeData(data) }.onSuccess { items in self.updateUI(items) }.onFailure { e in NSLog(e.description) } } 33 -
With Cache? func refreshWithCache() { let cached = Promise<Array<Dictionary<String, AnyObject>>>()
let url = "https://lobste.rs/hottest.json" future { if let cachedData = self.getCached() { cached.success(cachedData) } return retrieveData(url) >>= deserializeData }.onSuccess { items in self.updateUI(items) self.cacheItems(items) } cached.onSuccess { items in self.updateUI(items) } } 34 -
So what is a Monad? A Monad is a way
to create complexity, starting with simplicity. 35 -
Other famous Monads • I/O Monad • State Monad •
Reader/Writer Monad • Continuation Monad 37 -
What can be built with Monads? (Examples) • A Reactive
Framework, for combining streams • A DSL for SQLite, creating type safe queries • A toolset to deal with collections (e.g. LINQ) 38 -
Next? • Brian Beckman: Don't fear the Monad (video) •
Functional Programming in Swift (eBook) • Functional Programming (Coursera) • Principles of Reactive Programming (Coursera) 39 -
Resources • Example Project in Github • DeLorean Functional Reactive
Kit • BuzzDeCafe (Cat images) • SwiftZ Library • Deviant Art 40