introduction to functional reactive programming poznan
TRANSCRIPT
Introduction to Functional Reactive
Programming
@EliSawic
About me
Eliasz SawickiBlog: www.eliaszsawicki.comTwitter: @EliSawic
@EliSawic
Agenda• What is functional reactive programming?
• Working with streams
• ReactiveCocoa - Thinking in signals
• Example
@EliSawic
Functional Reactive Programming
@EliSawic
WikipediaFunctional reactive programming (FRP) is a programming paradigm for reactive programming (asynchronous dataflow programming) using the building blocks of functional programming (e.g. map, reduce, filter).
@EliSawic
Reactive Programming
@EliSawic
Asynchronous Dataflow
@EliSawic
Reacting to state changes
@EliSawic
Functional Programming
@EliSawic
Immutable
@EliSawic
assert(f(x) == f(x))
@EliSawic
A personclass Person { let name: String let phoneNumber: String init(name: String, phoneNumber: String) { self.name = name self.phoneNumber = phoneNumber }}
class MobilePhone { func call(person: Person) -> Bool { // implementation }}
@EliSawic
Mutablelet mobilePhone = MobilePhone()let john = Person(name: "John", phoneNumber: "123456789")
func makeAPhoneCall(device: Phone, person: Person, countryCode: String) -> Bool { person.phoneNumber = countryCode + person.phoneNumber let success = device.call(person) return success}
makeAPhoneCall(device: mobilePhone, person: john, countryCode: "+48") // truemakeAPhoneCall(device: mobilePhone, person: john, countryCode: "+48") // falsemakeAPhoneCall(device: mobilePhone, person: john, countryCode: "+48") // false
@EliSawic
Immutablelet mobilePhone = MobilePhone()let john = Person(name: "John", phoneNumber: "123456789")
func makeAPhoneCall(device: Phone, person: Person, countryCode: String) -> Bool { let prefixedPhoneNumber = countryCode + person.phoneNumber let newPerson = Person(name: person.name, phoneNumber: prefixedPhoneNumber) let success = device.call(newPerson) return success}
makeAPhoneCall(device: mobilePhone, person: john, countryCode: "+48") // truemakeAPhoneCall(device: mobilePhone, person: john, countryCode: "+48") // truemakeAPhoneCall(device: mobilePhone, person: john, countryCode: "+48") // true
@EliSawic
Stateless
@EliSawic
Statefulvar value = 0
func increment() -> Int { value += 1 return value}
@EliSawic
Statelessfunc increment(value: Int) -> Int { return value + 1}
@EliSawic
Imperative vs
Declarative
@EliSawic
Imperative
@EliSawic
Imperativelet array = [0, 1, 2, 3, 4, 5]var evenNumbers = [Int]()for element in array { if element % 2 == 0 { evenNumbers.append(element) }}
@EliSawic
Declarative
@EliSawic
Declarativelet array = [0, 1, 2, 3, 4, 5]let evenNumbers = array.filter { $0 % 2 == 0 }
@EliSawic
Working with streams
@EliSawic
Stream
@EliSawic
Manipulating streams
@EliSawic
Map
@EliSawic
Filter
@EliSawic
Aggregating
@EliSawic
Skip repeats
@EliSawic
Manipulating multiple streams
@EliSawic
Combine latest
@EliSawic
Zip
@EliSawic
Merge
@EliSawic
Chaining streams
@EliSawic
www.rxmarbles.com
@EliSawic
ReactiveCocoa
@EliSawic
Thinking in Signals
@EliSawic
What is a signal?
@EliSawic
This presentation is a signal
@EliSawic
Represents events over time
@EliSawic
No random access to events
@EliSawic
Observe and react
@EliSawic
If you don't listen, it's gone
@EliSawic
Observing does not trigger side effects
@EliSawic
What is event?
@EliSawic
Eventenum Quality { case Great case Average case Worst}
struct Idea {
var content: String var quality: Quality
}
@EliSawic
Non-Terminating• Next
@EliSawic
Terminating
• Completed
• Failed
• Interrupted (Reactive Cocoa)
@EliSawic
Presentationlet (presentation, presentationObserver) = Signal<Idea, NoError>.pipe()
let content = "This presentation is a signal"let idea = Idea(content: content, quality: .Great)
presentationObserver.send(value: idea)
@EliSawic
Observingpresentation.observeValues { idea in remember(idea: idea)}
presentation.observeCompleted { print("Finally...")}
presentationObserver.send(value: idea)presentationObserver.sendCompleted()
@EliSawic
Only great ideaslet greatIdeas = presentation.filter { $0.quality == .Great }greatIdeas.observeValues { (greatIdea) in remember(idea: greatIdea)}
presentationObserver.send(value: idea)presentationObserver.sendCompleted()
@EliSawic
Positive listenerlet greatPresentation = presentation.map { idea -> Idea in var greatIdea = idea greatIdea.quality = .Great return greatIdea}
@EliSawic
Count worst ideaslet worstIdeas = greatPresentation.filter { $0.quality == .Worst }
let numberOfWorstIdeas = worstIdeas.reduce(0) { (sum, idea) -> Int in return sum + 1}
numberOfWorstIdeas.observeValues { (numberOfWorstIdeas) in print("Number of worst ideas: \(numberOfWorstIdeas)")}
presentationObserver.send(value: idea)presentationObserver.sendCompleted()
@EliSawic
Signal's lifetime• Passes any number of Next events
• "Dies" when terminating event
• Any new observer will receive Interrupted event
@EliSawic
Signal producer
@EliSawic
Represents a tasks
@EliSawic
Creates a signal
@EliSawic
Possible side effects
@EliSawic
Does not start it's work if not asked
@EliSawic
Run presentationfunc runPresentation() -> SignalProducer<Idea, NoError> { return SignalProducer { observer, _ in observer.send(value: idea1) observer.send(value: idea2) ... observer.sendCompleted() }}
@EliSawic
Work with presentationrunPresentation().startWithSignal { (signal, _) in signal.observeValues({ idea in print(idea) })
signal.observeCompleted { print("Finally...") }}
@EliSawic
Cold vs Hot
@EliSawic
Properties
@EliSawic
Mutable Propertylet firstSlide = Slide(number: 1)let slide = MutableProperty<Slide>(firstSlide)
slide.producer.startWithNext { (text) in print(text)}
slide.value = Slide(number: 2)
@EliSawic
Bindings
@EliSawic
Binding examplelet slideNumber = MutableProperty<Int>(0)let (signal, _) = Signal<Slide, NoError>.pipe()slideNumber <~ signal.map { return $0.number }
label.reactive.text <~ signal.map { return "Slide number \($0.number)" }
@EliSawic
Schedulers
@EliSawic
Know where you aresignal.observe(on: QueueScheduler.main).observeValues { idea in print("Performing UI updates")}
producer.start(on: backgroundQueue).startWithValues { values in print("Starting task")}
@EliSawic
Memory Management
@EliSawic
Disposables
@EliSawic
Free your memorylet disposablesBag = CompositeDisposable()disposablesBag += signal.observeValues { value in ...}
disposablesBag += producePresentation().startWithValues { (value) in ...}
disposablesBag.dispose()
@EliSawic
Example
@EliSawic
@EliSawic
How does it work?
@EliSawic
Is name valid?let isValidName = nameSignal.map { (name) -> Bool in return input.characters.count > 2}
@EliSawic
Is surname valid?let isValidSurname = nameSignal.map { (name) -> Bool in return input.characters.count > 2}
@EliSawic
Is mail valid?let isValidMail = mailSignal.map { (mail) -> Bool in let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}" let emailTest = NSPredicate(format:"SELF MATCHES %@", emailRegEx) return emailTest.evaluateWithObject(mail)}
@EliSawic
Combine Latestlet formData = combineLatest(isValidName, isValidSurname, isValidMail)
@EliSawic
Is form valid?let isValidForm = MutableProperty<Bool>(false)
isValidForm <~ formData.map { (isValidName, isValidSurname, isValidMail) -> Bool in return isValidMail && isValidSurname && isValidMail}
@EliSawic
Button statelet producer = isValidForm.producer.skipRepeats()
producer.startWithNext { isValid in updateButtonWith(state: isValid)}
@EliSawic
Conclusion
@EliSawic
Thank you for your attention!sendCompleted()
@EliSawic