Taming Asynchronous
Transforms with
Interstellar
let me = Person(name: "Jens Ravens", company: "nerdgeschoss")
@JensRavens
GitHub: JensRavens
jensravens.com
nerdgeschoss.de
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
Optionals
and Results
Optionals are a box containing
something.
struct Cat {
func pet() -> String {
return "purrr"
}
}
let boxContainingCat: Optional<Cat> = Cat()
let sound: String?
if let cat = boxContainingCat {
sound = cat.pet()
} else {
sound = nil
}
struct Cat {
func pet() -> String {
return "purrr"
}
}
let boxContainingCat: Optional<Cat> = Cat()
let sound = boxContainingCat?.pet()
struct Cat {
func pet() -> String {
return "purrr"
}
}
let boxContainingCat: Optional<Cat> = Cat()
let sound = boxContainingCat.map { cat in cat.pet() }
struct Cat {
func pet() -> String? {
return "purrr"
}
}
let boxContainingCat: Optional<Cat> = Cat()
let sound: String?? = boxContainingCat.map {
cat in cat.pet()
}
struct Cat {
func pet() -> String? {
return "purrr"
}
}
let boxContainingCat: Optional<Cat> = Cat()
let sound: String? = boxContainingCat.flatMap {
cat in cat.pet()
}
func double(i: Int) -> [Int] { return [i, 2*i] }
[1, 2, 3].map(double) // [[1,2], [2,4], [3,6]]
[1, 2, 3].flatMap(double) // [[1, 2, 2,4, 3,6]
Error Handling
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!
enum Result<T> {
case Success(T)
case Error(ErrorType)
func map<U>(f: T -> U) -> Result<U>
func flatMap<U>(f: T -> Result<U>) -> Result<U>
}
func ls()-> Result<[String]>
func grep(pattern: String)(values: [String]) -> [String]
func sort(values: [String]) -> [String] { return [] }
let sorted = ls().map(grep("*.jpg")).map(sort)
ls | grep *.jpg | sort
Interstellar
ls | grep *.jpg | sort
class Signal<T> {
func subscribe(f: Result<T> -> Void) -> Signal<T>
func next(g: T -> Void) -> Signal<T>
func error(g: ErrorType -> Void) -> Signal<T>
func update(result: Result<T>)
func update(value: T)
func update(error: ErrorType)
}
let signal = Signal<String>()
signal.next { string in print(string) }
signal.update("Hello World")
pushing instead of pulling
But what about
Threads?
let threadSignal = Signal<String>()
func uppercase(string: String) -> String {
return string.uppercaseString
}
threadSignal
.ensure(Thread.background)
.map(uppercase)
.ensure(Thread.main)
.next { print($0) }
Extending UIKit to
support Signals.
extension UITextField {
public var textSignal: Signal<String>
}
let textField = UITextField()
textField.textSignal.next { string in print(string) }
If it’s variable, it qualifies
as a Signal.
real world code examples
func executeRequest(request: Request) -> Signal<HTTPResponse>
func getURL(response: HTTPResponse) throws -> NSURL
func upload(data: NSData)(url: NSURL, completion:
Result<String>->Void)
func createEntity(path: String, completion:
Result<Conversation>->Void)
func createConversation(avatar: UIImage) ->
Signal<Conversation> {
let signal = Signal<Conversation>()
let data = UIImageJPEGRepresentation(avatar, 0.8)!
api
.executeRequest(.GetUploadURL)
.flatMap(getURL)
.flatMap(api.upload(data))
.flatMap(createEntity)
.subscribe(signal.update)
return signal
}
func poll() -> Signal<[Conversation]> {
let signal = Signal<[Conversation]>()
let json = api
.executeRequest(.GetConversations)
.map { $0.json }
let conversations = json
.flatMap(Sync<Conversation>(context: context).updateObjects)
let messages = json
.flatMap(getMessages)
.flatMap(Sync<Message>(context: context).updateObjects)
conversations.merge(messages)
.flatMap(context.saveAndPipe)
.next { signal.update($0.0) }
.error { signal.update($0) }
signal.map(countUnread).next(setUnreadCount)
return signal
}
func poll() -> Signal<[Conversation]> {
let signal = Signal<[Conversation]>()
let json = api
.executeRequest(.GetConversations)
.map { $0.json }
let conversations = json
.flatMap(Sync<Conversation>(context: context).updateObjects)
let messages = json
.flatMap(getMessages)
.flatMap(Sync<Message>(context: context).updateObjects)
conversations.merge(messages)
.flatMap(context.saveAndPipe)
.next { signal.update($0.0) }
.error { signal.update($0) }
signal.map(countUnread).next(setUnreadCount)
return signal
}
API Request
sync conversations
sync messages
wait for both
then save
update unread
count
notify listeners
Warpdrive
• Thread.main / Thread.background
• Signal.delay(seconds: NSTimeInterval)
• Signal.wait throws
• Signal.debounce(seconds: NSTimeInterval)
Coming soon:
Holodeck UIKit Bindings
Thank you.
@JensRavens
github.com/jensravens/interstellar

Taming Asynchronous Transforms with Interstellar

  • 1.
  • 2.
    let me =Person(name: "Jens Ravens", company: "nerdgeschoss") @JensRavens GitHub: JensRavens jensravens.com nerdgeschoss.de
  • 3.
    A short introductionto functional programming, the universe and everything.
  • 4.
    In the beginningMcIlroy created the unix pipe. And he saw it was good. ls | grep *.jpg | sort
  • 5.
  • 6.
    Optionals are abox containing something.
  • 7.
    struct Cat { funcpet() -> String { return "purrr" } } let boxContainingCat: Optional<Cat> = Cat() let sound: String? if let cat = boxContainingCat { sound = cat.pet() } else { sound = nil }
  • 8.
    struct Cat { funcpet() -> String { return "purrr" } } let boxContainingCat: Optional<Cat> = Cat() let sound = boxContainingCat?.pet()
  • 9.
    struct Cat { funcpet() -> String { return "purrr" } } let boxContainingCat: Optional<Cat> = Cat() let sound = boxContainingCat.map { cat in cat.pet() }
  • 10.
    struct Cat { funcpet() -> String? { return "purrr" } } let boxContainingCat: Optional<Cat> = Cat() let sound: String?? = boxContainingCat.map { cat in cat.pet() }
  • 11.
    struct Cat { funcpet() -> String? { return "purrr" } } let boxContainingCat: Optional<Cat> = Cat() let sound: String? = boxContainingCat.flatMap { cat in cat.pet() }
  • 12.
    func double(i: Int)-> [Int] { return [i, 2*i] } [1, 2, 3].map(double) // [[1,2], [2,4], [3,6]] [1, 2, 3].flatMap(double) // [[1, 2, 2,4, 3,6]
  • 13.
  • 14.
    Buy it, useit, break it, fix it, Trash it, change it, mail - upgrade it. – Daft Punk, Technologic
  • 15.
    Buy it; if error{ //TODO: Handle me! } else { use it; if error { //TODO: Handle me! } else { break it; if error { //TODO: Handle me!
  • 16.
    enum Result<T> { caseSuccess(T) case Error(ErrorType) func map<U>(f: T -> U) -> Result<U> func flatMap<U>(f: T -> Result<U>) -> Result<U> }
  • 17.
    func ls()-> Result<[String]> funcgrep(pattern: String)(values: [String]) -> [String] func sort(values: [String]) -> [String] { return [] } let sorted = ls().map(grep("*.jpg")).map(sort) ls | grep *.jpg | sort
  • 18.
  • 19.
    ls | grep*.jpg | sort
  • 20.
    class Signal<T> { funcsubscribe(f: Result<T> -> Void) -> Signal<T> func next(g: T -> Void) -> Signal<T> func error(g: ErrorType -> Void) -> Signal<T> func update(result: Result<T>) func update(value: T) func update(error: ErrorType) } let signal = Signal<String>() signal.next { string in print(string) } signal.update("Hello World")
  • 21.
  • 22.
  • 23.
    let threadSignal =Signal<String>() func uppercase(string: String) -> String { return string.uppercaseString } threadSignal .ensure(Thread.background) .map(uppercase) .ensure(Thread.main) .next { print($0) }
  • 24.
  • 25.
    extension UITextField { publicvar textSignal: Signal<String> } let textField = UITextField() textField.textSignal.next { string in print(string) }
  • 26.
    If it’s variable,it qualifies as a Signal.
  • 27.
  • 28.
    func executeRequest(request: Request)-> Signal<HTTPResponse> func getURL(response: HTTPResponse) throws -> NSURL func upload(data: NSData)(url: NSURL, completion: Result<String>->Void) func createEntity(path: String, completion: Result<Conversation>->Void) func createConversation(avatar: UIImage) -> Signal<Conversation> { let signal = Signal<Conversation>() let data = UIImageJPEGRepresentation(avatar, 0.8)! api .executeRequest(.GetUploadURL) .flatMap(getURL) .flatMap(api.upload(data)) .flatMap(createEntity) .subscribe(signal.update) return signal }
  • 29.
    func poll() ->Signal<[Conversation]> { let signal = Signal<[Conversation]>() let json = api .executeRequest(.GetConversations) .map { $0.json } let conversations = json .flatMap(Sync<Conversation>(context: context).updateObjects) let messages = json .flatMap(getMessages) .flatMap(Sync<Message>(context: context).updateObjects) conversations.merge(messages) .flatMap(context.saveAndPipe) .next { signal.update($0.0) } .error { signal.update($0) } signal.map(countUnread).next(setUnreadCount) return signal }
  • 30.
    func poll() ->Signal<[Conversation]> { let signal = Signal<[Conversation]>() let json = api .executeRequest(.GetConversations) .map { $0.json } let conversations = json .flatMap(Sync<Conversation>(context: context).updateObjects) let messages = json .flatMap(getMessages) .flatMap(Sync<Message>(context: context).updateObjects) conversations.merge(messages) .flatMap(context.saveAndPipe) .next { signal.update($0.0) } .error { signal.update($0) } signal.map(countUnread).next(setUnreadCount) return signal } API Request sync conversations sync messages wait for both then save update unread count notify listeners
  • 31.
  • 32.
    • Thread.main /Thread.background • Signal.delay(seconds: NSTimeInterval) • Signal.wait throws • Signal.debounce(seconds: NSTimeInterval) Coming soon: Holodeck UIKit Bindings
  • 33.