|
| 1 | +# Notes |
| 2 | + |
| 3 | +Devlog, reverse chronological order. |
| 4 | + |
| 5 | +Contains rough notes and references while designing. |
| 6 | + |
| 7 | +## 2024-06-05 |
| 8 | + |
| 9 | +Proposed semantics after discussing with Berni. |
| 10 | + |
| 11 | +- Cells |
| 12 | + - Discrete |
| 13 | + - Use transactions to synchronize |
| 14 | + - Last-write-wins at input boundary (e.g. too many clicks results in last click winning). |
| 15 | +- Streams |
| 16 | + - Discrete |
| 17 | + - Uses separate transaction system to ensure one event is always one event |
| 18 | + - Buffered semantics. (e.g. too many clicks results in more transactions being added to queue) |
| 19 | + |
| 20 | +Implications: |
| 21 | + |
| 22 | +- Streams and events do not happen during shared moments |
| 23 | +- Streams may NEVER sample cell state |
| 24 | + - Since they aren't synchronized, sampling might mean seeing the graph in an inconsistent state. |
| 25 | + - Streams may only get values from upstream streams |
| 26 | + - However, you may produce a stream from cell changes |
| 27 | +- Streams are essentially "async" computation, while cells are "sync" |
| 28 | + |
| 29 | +--- |
| 30 | + |
| 31 | +> Really quickly: reactive-banana is definitely pull-based not push-pull. reactive is push-pull. Yampa and netwire are arrowized. There are FRPs which allow "accumulating values" but don't allow "switching", FRPs which allow "switching" but not "accumulating values". Both of those are "simple" FRP. Arrowized FRP allows switching and accumulating and uses arrows to control the danger of combining those features. Monadic FRP like reactive-banana, sodium, and elerea use other careful mechanisms to ensure that switching and accumulating don't interact too much. – [J. Abrahamson Oct 2, 2014 at 20:04](https://stackoverflow.com/questions/26164135/how-fundamentally-different-are-push-pull-and-arrowized-frp#comment41026849_26164135) |
| 32 | +
|
| 33 | +> Arrowized FRP also has the neat feature that signals are always stated in context of their inputs which lets you transform the outputs covariantly and the inputs contravariantly in order to better simulate interactive FRP. See Genuinely Functional User Interfaces by Courtney and Elliott for a great example of that feature. – [J. Abrahamson Oct 2, 2014 at 20:05](https://stackoverflow.com/questions/26164135/how-fundamentally-different-are-push-pull-and-arrowized-frp#comment41026887_26164135) |
| 34 | +
|
| 35 | + |
| 36 | +## 2024-05-30 |
| 37 | + |
| 38 | +### Design |
| 39 | + |
| 40 | +- Discrete classical FRP |
| 41 | + - Streams |
| 42 | + - Events over time. Update during a moment. |
| 43 | + - Independent. They may not depend upon each other's state. |
| 44 | + - They may depend upon cells, but must get the cell's state before |
| 45 | + the cell graph is updated. |
| 46 | + - They act as IO input to the cell graph. |
| 47 | + - Cells |
| 48 | + - Reactive containers for state. Always have a value. |
| 49 | + - Computed Cells |
| 50 | + - Reactive computed states, derived from cells and other computed cells. |
| 51 | + |
| 52 | +### Implementation |
| 53 | + |
| 54 | +- Transaction |
| 55 | + - Update streams: Update streams: |
| 56 | + - Update cells: mutate cell state and mark computed cells dirty (push) |
| 57 | + - Dispatch "I am dirty" notification immediately during cell update phase to downstream cells |
| 58 | + - Update sinks: get updated cell and computed state. Computed state is recomputed if dirty. |
| 59 | + - Subscribe with sinks |
| 60 | + |
| 61 | +### Restricted operators |
| 62 | + |
| 63 | +- It may be worth offering operators that do not allow arbitrary Turing-complete function definitions. E.g. a restricted subset of data operators taken from SQL or Linq |
| 64 | + - `select(keyPath: string)` - a restricted form of map |
| 65 | + - `where(selector: formula)` - a restricted form of filter |
| 66 | + - `groupBy()` |
| 67 | + - `orderBy()` |
| 68 | + - `union()`, `intersect()` - restricted forms of join/merge |
| 69 | + - `count()`, `min()`, `max()`, `sum()`, `avg()`, `truncate()` - restricted forms of computation |
| 70 | + |
| 71 | +### Classical FRP |
| 72 | + |
| 73 | +Qualities: |
| 74 | + |
| 75 | +- Comes in discrete and continuous flavors. We only care about discrete for our purposes. |
| 76 | +- Behaviors and events (also called cells and streams, or signals and streams) |
| 77 | + - Behaviors are reactive containers for state (think spreadsheet cell) |
| 78 | + - Events are streams of events over time |
| 79 | + - Both are synchronized by a transaction system that makes sure changes happen during discrete "moments" in time, and inconsistent graph states are not observable. |
| 80 | +- Resulting computation graph is pure. |
| 81 | +- Theory pioneered by Conal Elliott. |
| 82 | +- 10 primitives: |
| 83 | + - map, merge, hold, snapshot, filter, lift, never, constant, sample, and switch. (Functional Reactive Programming, 2.3, Manning) |
| 84 | + |
| 85 | +> Each FRP system has its own policy for merging simultaneous events. Sodium’s policy is as follows: |
| 86 | +> |
| 87 | +> - If the input events on the two input streams are simultaneous, merge combines them into one. merge takes a combining function as a second argument for this purpose. The signature of the combining function is A combine(A left, A right). |
| 88 | +> -The combining function is not used in the (usually more common) case where the input events are not simultaneous. |
| 89 | +> -You invoke merge like this: s1.merge(s2, f). If merge needs to combine simul- taneous events, the event from s1 is passed as the left argument of the combin- ing function f, and the event from s2 is passed on the right. |
| 90 | +> -The s1.orElse(s2) variant of merge doesn’t take a combining function. In the simultaneous case, the left s1 event takes precedence and the right s2 event is dropped. This is equivalent to s1.merge(s2, (l, r) -> l). The name orElse() was chosen to remind you to be careful, because events can be dropped. |
| 91 | +> |
| 92 | +> This policy has some nice results: |
| 93 | +> - There can only ever be one event per transaction in a given stream. |
| 94 | +> - There’s no such thing as event-processing order within a transaction. All events that occur in different streams within the same transaction are truly simultaneous in that there’s no detectable order between them. |
| 95 | +> |
| 96 | +> (Functional Reactive Programming, 2.6.1, Manning) |
| 97 | +
|
| 98 | +Talks: |
| 99 | + |
| 100 | +- [A More Elegant Specification for Functional Reactive Programming, Conal Elliott](https://www.youtube.com/watch?v=9vMFHLHq7Y0) |
| 101 | + |
| 102 | +Libraries: |
| 103 | + |
| 104 | +- [Sodium FRP](https://github.com/SodiumFRP) |
| 105 | + - [Sodium Typescript](https://github.com/SodiumFRP/sodium-typescript/tree/master/src/lib/sodium) |
| 106 | + |
| 107 | +### Signals |
| 108 | + |
| 109 | +Qualities: |
| 110 | + |
| 111 | +- Combines event streams and behaviors into a single concept. |
| 112 | +- Often uses single-transaction callback registration technique developed by S.js |
| 113 | +- Often uses push-pull FRP |
| 114 | +- Often uses closure and a "reactive scope" stack machine with a helper like `useEffect()` to register listeners |
| 115 | + |
| 116 | +Libraries: |
| 117 | + |
| 118 | +- [TC39 Signals Proposal](https://github.com/tc39/proposal-signals) |
| 119 | +- [SolidJS](https://www.solidjs.com/) |
| 120 | +- [Preact Signals](https://preactjs.com/guide/v10/signals/) |
| 121 | +- [S.js](https://github.com/adamhaile/S) |
| 122 | + - implements transactions and dynamic graph with single-transaction callback registration, eliminating listener memory leaks. |
| 123 | +- [Arrow.js](https://www.arrow-js.com/docs/) |
| 124 | + - Focuses on reactive objects, rather than values |
| 125 | + - Key-path style indexing using Proxy |
| 126 | +- [Elm Signals 3.0.0](https://github.com/elm-lang/core/blob/3.0.0/src/Native/Signal.js). Deprecated, but the implementation can be found here. 1st order FRP. |
| 127 | + |
| 128 | +### Observables |
| 129 | + |
| 130 | +Qualities: |
| 131 | + |
| 132 | +- No transaction system |
| 133 | +- No state (streams only) |
| 134 | +- Largely static graphs (1st-order FRP) with explicit subscription cancellation for dynamic graphs |
| 135 | + |
| 136 | +Libraries: |
| 137 | + |
| 138 | +- [RxJS](https://rxjs.dev/) |
| 139 | +- [TC39 Observable Proposal](https://github.com/tc39/proposal-observable) |
| 140 | +- [Apple Combine](https://developer.apple.com/documentation/combine/) |
| 141 | + |
| 142 | +### Concepts |
| 143 | + |
| 144 | +- Paper: [Push-pull FRP](http://conal.net/papers/push-pull-frp/push-pull-frp.pdf), Conal Elliott |
| 145 | +- Book: [Functional Reactive Programming](https://www.manning.com/books/functional-reactive-programming), Manning 2016 |
| 146 | +- Talk: [Controlling space and time: understanding the many formulations of FRP](https://www.youtube.com/watch?v=Agu6jipKfYw), Evan Czaplicki |
| 147 | +- Blog: [The Evolution of Signals in Javascript](https://dev.to/this-is-learning/the-evolution-of-signals-in-javascript-8ob). Notes on low-level implementation of signal libraries, from the creator of SolidJS. |
| 148 | +- Blog: [Introduction to fine-grained reactivity](https://dev.to/ryansolid/a-hands-on-introduction-to-fine-grained-reactivity-3ndf) from the creator of SolidJS. |
| 149 | +- GitHub: [General Theory of Reactivity](https://github.com/kriskowal/gtor) |
| 150 | + |
| 151 | +### Cold vs. Hot Observables |
| 152 | + |
| 153 | +The distinction between cold and hot observables (or their equivalent) is a significant part of many FRP systems. Cold observables are those where the data-producing sequence starts anew for each subscriber, whereas hot observables share a single execution path among all subscribers. This distinction affects how data streams are multicast to multiple observers. |
| 154 | + |
| 155 | +- Cold observable: creates a data producer for each subscriber. |
| 156 | + - The observable is a pure transformation over the data producer. |
| 157 | + - Examples: [Reducers in Clojure](https://clojure.org/reference/reducers), [RxJS Observables](https://rxjs.dev). |
| 158 | +- Hot observable: Multicast. Maintains a list of subscribers and dispatches to them from a single data producing source. |
| 159 | + - Examples: [share](https://rxjs.dev/api/index/function/share) in RxJS. |
| 160 | + - Has to deal with callback cleanup bookkeeping in dynamic graphs. |
| 161 | + |
| 162 | +### Pipeable operators |
| 163 | + |
| 164 | +Following RxJS, we enable piping through unary functions. This is a functional alternative to method chaining. |
| 165 | + |
| 166 | +> Problems with the patched operators for dot-chaining are: |
| 167 | +> |
| 168 | +> Any library that imports a patch operator will augment the Observable.prototype for all consumers of that library, creating blind dependencies. If the library removes their usage, they unknowingly break everyone else. With pipeables, you have to import the operators you need into each file you use them in. |
| 169 | +> |
| 170 | +> Operators patched directly onto the prototype are not "tree-shakeable" by tools like rollup or webpack. Pipeable operators will be as they are just functions pulled in from modules directly. |
| 171 | +> |
| 172 | +> Unused operators that are being imported in apps cannot be detected reliably by any sort of build tool or lint rule. That means that you might import scan, but stop using it, and it's still being added to your output bundle. With pipeable operators, if you're not using it, a lint rule can pick it up for you. |
| 173 | +> |
| 174 | +> Functional composition is awesome. Building your own custom operators becomes much easier, and now they work and look just like all other operators in rxjs. You don't need to extend Observable or override lift anymore. |
| 175 | +
|
| 176 | +> [Pipeable Operators](https://v6.rxjs.dev/guide/v6/pipeable-operators) |
| 177 | +
|
| 178 | +And: |
| 179 | + |
| 180 | +> "pipeable" operators is the current and recommended way of using operators since RxJS 5.5. The main difference is that it's easier to make custom operators and that it's better treeshakable while not altering some global Observable object that could possible make collisions if two different parties wanted to create an operator of the same name. - [StackOverflow](https://stackoverflow.com/questions/48668701/what-is-pipe-for-in-rxjs) |
0 commit comments