introduction to functional reactive programming poznan
TRANSCRIPT
![Page 1: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/1.jpg)
Introduction to Functional Reactive
Programming
@EliSawic
![Page 2: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/2.jpg)
About me
Eliasz SawickiBlog: www.eliaszsawicki.comTwitter: @EliSawic
@EliSawic
![Page 3: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/3.jpg)
Agenda• What is functional reactive programming?
• Working with streams
• ReactiveCocoa - Thinking in signals
• Example
@EliSawic
![Page 4: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/4.jpg)
Functional Reactive Programming
@EliSawic
![Page 5: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/5.jpg)
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
![Page 6: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/6.jpg)
Reactive Programming
@EliSawic
![Page 7: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/7.jpg)
Asynchronous Dataflow
@EliSawic
![Page 8: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/8.jpg)
Reacting to state changes
@EliSawic
![Page 9: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/9.jpg)
Functional Programming
@EliSawic
![Page 10: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/10.jpg)
Immutable
@EliSawic
![Page 11: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/11.jpg)
assert(f(x) == f(x))
@EliSawic
![Page 12: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/12.jpg)
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
![Page 13: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/13.jpg)
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
![Page 14: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/14.jpg)
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
![Page 15: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/15.jpg)
Stateless
@EliSawic
![Page 16: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/16.jpg)
Statefulvar value = 0
func increment() -> Int { value += 1 return value}
@EliSawic
![Page 17: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/17.jpg)
Statelessfunc increment(value: Int) -> Int { return value + 1}
@EliSawic
![Page 18: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/18.jpg)
Imperative vs
Declarative
@EliSawic
![Page 19: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/19.jpg)
Imperative
@EliSawic
![Page 20: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/20.jpg)
Imperativelet array = [0, 1, 2, 3, 4, 5]var evenNumbers = [Int]()for element in array { if element % 2 == 0 { evenNumbers.append(element) }}
@EliSawic
![Page 21: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/21.jpg)
Declarative
@EliSawic
![Page 22: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/22.jpg)
Declarativelet array = [0, 1, 2, 3, 4, 5]let evenNumbers = array.filter { $0 % 2 == 0 }
@EliSawic
![Page 23: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/23.jpg)
Working with streams
@EliSawic
![Page 24: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/24.jpg)
Stream
@EliSawic
![Page 25: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/25.jpg)
Manipulating streams
@EliSawic
![Page 26: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/26.jpg)
Map
@EliSawic
![Page 27: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/27.jpg)
Filter
@EliSawic
![Page 28: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/28.jpg)
Aggregating
@EliSawic
![Page 29: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/29.jpg)
Skip repeats
@EliSawic
![Page 30: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/30.jpg)
Manipulating multiple streams
@EliSawic
![Page 31: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/31.jpg)
Combine latest
@EliSawic
![Page 32: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/32.jpg)
Zip
@EliSawic
![Page 33: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/33.jpg)
Merge
@EliSawic
![Page 34: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/34.jpg)
Chaining streams
@EliSawic
![Page 35: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/35.jpg)
www.rxmarbles.com
@EliSawic
![Page 36: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/36.jpg)
ReactiveCocoa
@EliSawic
![Page 37: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/37.jpg)
Thinking in Signals
@EliSawic
![Page 38: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/38.jpg)
What is a signal?
@EliSawic
![Page 39: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/39.jpg)
This presentation is a signal
@EliSawic
![Page 40: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/40.jpg)
Represents events over time
@EliSawic
![Page 41: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/41.jpg)
No random access to events
@EliSawic
![Page 42: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/42.jpg)
Observe and react
@EliSawic
![Page 43: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/43.jpg)
If you don't listen, it's gone
@EliSawic
![Page 44: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/44.jpg)
Observing does not trigger side effects
@EliSawic
![Page 45: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/45.jpg)
What is event?
@EliSawic
![Page 46: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/46.jpg)
Eventenum Quality { case Great case Average case Worst}
struct Idea {
var content: String var quality: Quality
}
@EliSawic
![Page 47: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/47.jpg)
Non-Terminating• Next
@EliSawic
![Page 48: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/48.jpg)
Terminating
• Completed
• Failed
• Interrupted (Reactive Cocoa)
@EliSawic
![Page 49: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/49.jpg)
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
![Page 50: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/50.jpg)
Observingpresentation.observeValues { idea in remember(idea: idea)}
presentation.observeCompleted { print("Finally...")}
presentationObserver.send(value: idea)presentationObserver.sendCompleted()
@EliSawic
![Page 51: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/51.jpg)
Only great ideaslet greatIdeas = presentation.filter { $0.quality == .Great }greatIdeas.observeValues { (greatIdea) in remember(idea: greatIdea)}
presentationObserver.send(value: idea)presentationObserver.sendCompleted()
@EliSawic
![Page 52: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/52.jpg)
Positive listenerlet greatPresentation = presentation.map { idea -> Idea in var greatIdea = idea greatIdea.quality = .Great return greatIdea}
@EliSawic
![Page 53: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/53.jpg)
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
![Page 54: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/54.jpg)
Signal's lifetime• Passes any number of Next events
• "Dies" when terminating event
• Any new observer will receive Interrupted event
@EliSawic
![Page 55: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/55.jpg)
Signal producer
@EliSawic
![Page 56: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/56.jpg)
Represents a tasks
@EliSawic
![Page 57: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/57.jpg)
Creates a signal
@EliSawic
![Page 58: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/58.jpg)
Possible side effects
@EliSawic
![Page 59: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/59.jpg)
Does not start it's work if not asked
@EliSawic
![Page 60: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/60.jpg)
Run presentationfunc runPresentation() -> SignalProducer<Idea, NoError> { return SignalProducer { observer, _ in observer.send(value: idea1) observer.send(value: idea2) ... observer.sendCompleted() }}
@EliSawic
![Page 61: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/61.jpg)
Work with presentationrunPresentation().startWithSignal { (signal, _) in signal.observeValues({ idea in print(idea) })
signal.observeCompleted { print("Finally...") }}
@EliSawic
![Page 62: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/62.jpg)
Cold vs Hot
@EliSawic
![Page 63: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/63.jpg)
Properties
@EliSawic
![Page 64: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/64.jpg)
Mutable Propertylet firstSlide = Slide(number: 1)let slide = MutableProperty<Slide>(firstSlide)
slide.producer.startWithNext { (text) in print(text)}
slide.value = Slide(number: 2)
@EliSawic
![Page 65: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/65.jpg)
Bindings
@EliSawic
![Page 66: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/66.jpg)
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
![Page 67: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/67.jpg)
Schedulers
@EliSawic
![Page 68: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/68.jpg)
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
![Page 69: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/69.jpg)
Memory Management
@EliSawic
![Page 70: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/70.jpg)
Disposables
@EliSawic
![Page 71: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/71.jpg)
Free your memorylet disposablesBag = CompositeDisposable()disposablesBag += signal.observeValues { value in ...}
disposablesBag += producePresentation().startWithValues { (value) in ...}
disposablesBag.dispose()
@EliSawic
![Page 72: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/72.jpg)
Example
@EliSawic
![Page 73: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/73.jpg)
@EliSawic
![Page 74: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/74.jpg)
How does it work?
@EliSawic
![Page 75: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/75.jpg)
Is name valid?let isValidName = nameSignal.map { (name) -> Bool in return input.characters.count > 2}
@EliSawic
![Page 76: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/76.jpg)
Is surname valid?let isValidSurname = nameSignal.map { (name) -> Bool in return input.characters.count > 2}
@EliSawic
![Page 77: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/77.jpg)
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
![Page 78: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/78.jpg)
Combine Latestlet formData = combineLatest(isValidName, isValidSurname, isValidMail)
@EliSawic
![Page 79: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/79.jpg)
Is form valid?let isValidForm = MutableProperty<Bool>(false)
isValidForm <~ formData.map { (isValidName, isValidSurname, isValidMail) -> Bool in return isValidMail && isValidSurname && isValidMail}
@EliSawic
![Page 80: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/80.jpg)
Button statelet producer = isValidForm.producer.skipRepeats()
producer.startWithNext { isValid in updateButtonWith(state: isValid)}
@EliSawic
![Page 81: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/81.jpg)
Conclusion
@EliSawic
![Page 82: Introduction To Functional Reactive Programming Poznan](https://reader034.vdocuments.us/reader034/viewer/2022051507/58729eda1a28ab07208b54c5/html5/thumbnails/82.jpg)
Thank you for your attention!sendCompleted()
@EliSawic