BaseObservable is the same as Observable, it just lives in a separate trait for technical reasons (the Self type param).
All Observables are lazy. An Observable starts when it gets its first observer (internal or external), and stops when it loses its last observer (again, internal or external).
Basic idea: Lazy Observable only holds references to those children that have any observers (either directly on themselves, or on any of their descendants). What this achieves:
Stream only propagates its value to children that (directly or not) have observers
Stream calculates its value only once regardless of how many observers / children it has) (so, all streams are "hot" observables)
Stream doesn't hold references to Streams that no one observes, allowing those Streams to be garbage collected if they are otherwise unreachable (which they should become when their subscriptions are killed by their owners)
Airstream may internally use Scala library functions which use == or hashCode for equality, for example List.contains. Comparing observables by structural equality pretty much never makes sense, yet it's not that hard to run into that, all you need is to create a case class subclass, and the Scala compiler will generate a structural-equality equals and hashCode methods for you behind the scenes.
Airstream may internally use Scala library functions which use == or hashCode for equality, for example List.contains. Comparing observables by structural equality pretty much never makes sense, yet it's not that hard to run into that, all you need is to create a case class subclass, and the Scala compiler will generate a structural-equality equals and hashCode methods for you behind the scenes.
To prevent that, we make equals and hashCode methods final, using the default implementation (which is reference equality).
Execute a side effecting callback every time the observable emits. If it's a signal, it also runs when its initial value is evaluated.
Execute a side effecting callback every time the observable emits. If it's a signal, it also runs when its initial value is evaluated.
Note: some signals such as stream.startWith or signal.composeAll have cacheInitialValue config, which could affect the number of times this callback is called in cases when you re-start a signal that was previously started, but has never emitted events before. In such cases, if cacheInitialValue == false (the default) , it will cause the signal's initial value to be re-evaluated, and this will in turn trigger the provided tapEach callback.
// #TODO[API] How to better type this? Note: Do not provide a callback that returns a LAZY value such as EventStream, it will not be started. I may eventually add a flatTapEach method for this.
Note: Calling tapEach on an observable does not add observers to it. The callback will only run if something else is listening to the observable.
Note: The primary method of running side effects in Airstream is putting them into Observers. In general, it's good practice to keep Observables themselves free of side effects. Airstream is actually pretty good at handling non-"pure" observables, but it's better to have such separation of concerns for your own sanity and the code's predictability / maintainability.
Note: This method is called tapEach for consistency with Scala collections. Scala also has a general tap method available by importing util.chaining.*.
Set the display name for this instance (observable or observer).
Set the display name for this instance (observable or observer).
This method modifies the instance and returns this. It does not create a new instance.
New name you set will override the previous name, if any. This might change in the future. For the sake of sanity, don't call this more than once for the same instance.
If display name is set, toString will output it instead of the standard type@hashcode string