Upload
jens-ravens
View
327
Download
2
Embed Size (px)
Citation preview
How to use Functional Reactive Programming without Black Magic
let me = Person(name: "Jens Ravens")
@JensRavens GitHub: JensRavens nerdgeschoss.de
swift.berlin
A short introduction to functional programming, the universe and everything.
In the beginning McIlroy created the unix pipe. And he
saw it was good.
ls | grep *.jpg | sort
Application Architecture
starting from a blank slate
implement to understand
And I promise not to use the scary M-word.
me, 12 weeks ago.
How to train your monad.
–Saunders Mac Lane
All told, a monad is just a monoid in the category of endofunctors.“
Monads are just Applicative Functors“
ls | grep *.jpg | sort
Buy it, use it, break it, fix it,
Trash it, change it, mail - upgrade it.
– Daft Punk, Technologic
Buy it; if error { //TODO: Handle me! } else { use it; if error { //TODO: Handle me! } else { break it; if error { //TODO: Handle me!
ls | grep *.jpg | sort
Monadsomething that defines map and bind
The optional Monadlet string: String? = "World" let greeting = string.map{"Hello \($0)”} //Hello World
extension Optional { func bind<U> (f: T->U?) -> U? { if let result = self.map({f($0)}) { return result } else { return nil } } }
The optional Monadlet string: String? = "World" let greeting = string.map{"Hello \($0)”} //Hello World
func greetOrNil(name: String)->String? { if name == "World" { return "Hello World" } else { return nil } }
let greeting2 = string.bind(greetOrNil) //Hello World
The optional Monadextension Optional { func bind<U> (f: T->U?) -> U? { if let result = self.map({f($0)}) { return result } else { return nil } } }
extension Optional { func bind<U> (f: T->Optional<U>) -> Optional<U> { switch self { case let .Some(value): return f(value) case .None: return nil } } }
The result Monad
public enum Result<T> { case Success(Box<T>) case Error(NSError) }
The result Monadpublic enum Result<T> { …
public func map<U>(f: T -> U) -> Result<U> { switch self { case let .Success(v):
return .Success(Box(f(v.value))) case let .Error(error): return .Error(error) } } …
}
The result Monadpublic enum Result<T> { …
public func bind<U>(f: T -> Result<U>) -> Result<U> { switch self { case let .Success(v): return f(v.value) case let .Error(error): return .Error(error) } } …
}
ls | grep *.jpg | sort
Monad
Transform
func parseString(data: NSData) -> Result<String>
func parseJson(data: NSData) -> Result<[String: AnyObject]>
func asyncGreeting(name: String, completion: Result<String>->Void)
public func bind<U>(f:(T, (Result<U>->Void))->Void) -> (Result<U>->Void)->Void { return { g in switch self { case let .Success(v): f(v.value, g) case let .Error(error): g(.Error(error)) } } }
Interstellar
ls | grep *.jpg | sort
public final class Signal<T> { private var value: Result<T>? private var callbacks: [Result<T> -> Void] = [] public func subscribe(f: Result<T> -> Void) { if let value = value { f(value) } callbacks.append(f) }
public func update(value: Result<T>) { self.value = value self.callbacks.map{$0(value)} }
}
public func map<U>(f: T -> U) -> Signal<U> { let signal = Signal<U>() subscribe { result in signal.update(result.map(f)) } return signal } public func bind<U>(f: T -> Result<U>) -> Signal<U> { let signal = Signal<U>() subscribe { result in signal.update(result.bind(f)) } return signal }
public func bind<U>(f: (T, (Result<U>->Void))->Void) -> Signal<U> { let signal = Signal<U>() subscribe { value in value.bind(f)(signal.update) } return signal }
pushing instead of pulling
the rise of custom operators
infix operator >>> { associativity left precedence 160 }
public func >>> <A,B> (left: Signal<A>, right: A->Result<B>) -> Signal<B> { return left.bind(right) }
public func >>> <A,B>(left: Signal<A>, right: (A, (Result<B>->Void))->Void) -> Signal<B>{ return left.bind(right) }
public func >>> <A,B> (left: Signal<A>, right: A->B) -> Signal<B> { return left.map(right) }
ls | grep *.jpg | sort
ls | grep *.jpg | sort
ls >>> grep("*.jpg") >>> sort
But what about Threads?
public final class Thread { public static func main<T>(a: T, completion: T->Void) { dispatch_async(dispatch_get_main_queue()) { completion(a) } } public static func background<T>(queue: dispatch_queue_t)(_ a: T, _ completion: T->Void) { dispatch_async(queue){ completion(a) } } }
ls >>> Thread.background(queue) >>> grep("*.jpg") >>> sort >>> Thread.main
Extending UIKit to support Signals.
var SignalHandle: UInt8 = 0 extension UITextField { public var textSignal: Signal<String> { let signal: Signal<String> if let handle = objc_getAssociatedObject(self, &SignalHandle) as? Signal<String> { signal = handle } else { signal = Signal("") NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("textChanged:"), name: UITextFieldTextDidChangeNotification, object: self) objc_setAssociatedObject(self, &SignalHandle, signal, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN_NONATOMIC)) } return signal } public func textChanged(notification: NSNotification) { textSignal.update(.Success(Box(self.text))) } }
If it’s variable, it qualifies as a Signal.
ReactiveKittenIt’s about gifs. And cats.
And gifs of cats.
userTyping >>> createURL >>> loadRequest >>> parseData >>> mainThread >>>
displayCats
The transform, the cat and you.
import Interstellar
private func request(path: String, completion: Result<NSData>->Void) { let url = NSURL(string: baseURL.stringByAppendingString(path))! let request = NSURLRequest(URL: url) session.dataTaskWithRequest(request){ data, response, error in if error != nil { completion(.Error(error)) } else if let response = response as? NSHTTPURLResponse { if response.statusCode >= 200 && response.statusCode<300 { completion(.Success(Box(data))) } else { completion(.Error(NSError(domain: "Networking", code: response.statusCode, userInfo: nil))) } } else { completion(.Error(NSError(domain: "Networking", code: 500, userInfo: nil))) } }.resume() }
private func parseJSON(data: NSData) ->Result<[String: AnyObject]> { var error: NSError? if let json = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &error) as? [String: AnyObject] { return .Success(Box(json)) } else { return .Error(error!) } }
let imageSignal = gifSignal >>> getURL >>> Thread.background >>> loadFromCache >>> retryFromNetwork >>> Thread.main
class ViewController: UIViewController { var signal: Signal<[Gif]>! let searchBar = UISearchBar() override func viewDidLoad() { super.viewDidLoad()
navigationItem.titleView = searchBar signal = searchBar.textSignal >>> Network().search() >>> Thread.main
What’s next()?
Functors, Applicatives and Monads in Pictures.
http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html
The Introduction to RP you’ve been missing.
https://gist.github.com/staltz/868e7e9bc2a7b8c1f754
ReactiveCocoa 3
RxSwift
Interstellar
available on Carthage jensravens/interstellar