Mirror: 馃帺 A tiny but capable push & pull stream library for TypeScript and Flow
1--- 2title: Architecture 3order: 1 4--- 5 6It may be useful to understand how Wonka's sources work internally 7if you want to write a new operator from scratch or contribute to it. 8 9This section explains how Wonka works internally and how it differs from 10the callbag specification. 11 12## Just Functions 13 14Internally Wonka only uses functions with rather simple signatures to 15make its streams work. 16 17We have sinks on one end, which need to receive values, and sources 18on the other, which need to send values. 19The sink is therefore just a function that we call with values over time. 20This is called a "push" signal. 21 22Because a sink has a start, incoming values, and an end, there are three 23signals that a sink can receive: `Start`, `Push`, and `End`. 24 25``` reason 26type signalT('a) = 27 | Start 28 | Push('a) 29 | End; 30 31type sinkT('a) = (. signalT('a)) => unit; 32``` 33 34As shown, the sink is just a function accepting a signal as its argument. 35 36When the stream starts then the sink is called with `Start`, 37Then for every incoming, new value it's called with `Push('a)`, 38and when the stream ends it's finally called with `End`. 39 40Since we want a source to send these values to the sink, the source is 41also just a function and it accepts a sink as its argument. 42 43``` reason 44type sourceT('a) = sinkT('a) => unit; 45``` 46 47This is completely sufficient to represent simple "push" streams, where 48values are pushed from the source to the sink. They essentially flow from 49the "top" to the "bottom". 50 51Operators are just functions that transform a source. They take a 52source and some number of arguments and return a new source. 53Internally they may also create a new sink function that wraps the 54sink that their source will be called with. 55 56The type signature of an operator with no other arguments is thus: 57 58``` reason 59type operatorT('a, 'b) = sourceT('a) => sourceT('b); 60/* which is the same as: */ 61type operatorT('a, 'b) = (sourceT('a), sinkT('b)) => unit; 62``` 63 64## Adding Callbacks 65 66To complete this pattern we're still missing a single piece: callbacks! 67 68Previously, we've looked at how sources are functions that accept sinks, which 69in turn are functions accepting a signal. What we're now missing is what makes 70Wonka's streams also work as iterables. 71 72We'd also like to be able to _cancel_ streams, so that we can interrupt 73them and not receive any more values. 74 75We can achieve this by passing a callback function on when a stream starts. 76In Wonka, a sink's `Start` signal also carries a callback that is used to communicate 77back to the source, making these "talkback signals" flow from the bottom to the top. 78 79``` reason 80type talkbackT = 81 | Pull 82 | Close; 83 84type signalT('a) = 85 | Start((. talkbackT) => unit) 86 | Push('a) 87 | End; 88``` 89 90This is like the previous `signalT('a)` definition, but the `Start` signal has the 91callback definition now. The callback accepts one of two signals: `Pull` or `Close`. 92 93`Close` is a signal that will cancel the stream. It tells the source to stop sending 94new values. 95 96The `Pull` signal is a signal that asks the source to send the next value. This is 97especially useful to represent iterables. In practice a user would never send this 98signal explicitly, but sinks would send the signal automatically after receiving the 99previous value from the stream. 100 101In asynchronous streams the `Pull` signal is of course a no-op. It won't do 102anything since we can't ask for asynchronous values. 103 104## Comparison to Callbags 105 106This is the full pattern of Wonka's streams and it's a little different from callbags. 107These changes have been made to make Wonka's streams typesafe. But there's 108also a small omission that makes Wonka's streams easier to explain. 109 110In Callbags, sources don't just accept sinks as their only argument. In fact, in 111callbags the source would also receive three different signals. This can be useful 112to represent "subjects". 113 114A subject is a sink and source combined. It can be used to dispatch values imperatively, 115like an event dispatcher. 116 117In Wonka there's a separate type for subjects however, since this reduces the 118complexity of its streams a lot: 119 120``` reason 121type subjectT('a) = { 122 source: sourceT('a), 123 next: 'a => unit, 124 complete: unit => unit, 125}; 126``` 127 128Hence in Wonka a subject is simply a wrapper around a source and a `next` and `complete` 129method.