Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Monads are Not Monsters - UIKonf 2015

Monads are Not Monsters - UIKonf 2015

UIKonf 2015 talk Monads are not Monsters.

Junior B.

May 18, 2015
Tweet

More Decks by Junior B.

Other Decks in Programming

Transcript

  1. 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
  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
  3. 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
  4. 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
  5. 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
  6. 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
  7. ...and the operator // Operator definition public func >>= <T,U>(lhs:

    Optional<T>, rhs: T -> Optional<U>) -> Optional<U> { return lhs.flatMap(rhs) } 12
  8. Crash Free - Version 2 func refreshData() { let url

    = "https://lobste.rs/hottest.json" if let result = retrieveData(url) >>= self.deserializeData { updateUI(result) } } 13
  9. 16

  10. With Optionals we are crash free, but what about errors?

    We need to handle errors... How? 18
  11. 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
  12. 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
  13. What is the result? (Version 3) func refreshData() { let

    url = "https://lobste.rs/hottest.json" (retrieveData(url) >>= deserializeData) >>- updateUI } 21
  14. 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
  15. 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
  16. 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
  17. 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
  18. How Future Works? A future needs: • An execution context

    to run (Queue) • A task to complete 28
  19. Eventually • An 'on success' block to run • An

    'on error' block to run • An 'on complete' block run 29
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. So what is a Monad? A Monad is a way

    to create complexity, starting with simplicity. 35
  26. 36

  27. Other famous Monads • I/O Monad • State Monad •

    Reader/Writer Monad • Continuation Monad 37
  28. 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
  29. Next? • Brian Beckman: Don't fear the Monad (video) •

    Functional Programming in Swift (eBook) • Functional Programming (Coursera) • Principles of Reactive Programming (Coursera) 39
  30. Resources • Example Project in Github • DeLorean Functional Reactive

    Kit • BuzzDeCafe (Cat images) • SwiftZ Library • Deviant Art 40