Structs
The following structs are available globally.
-
Monadic implementation of continuation passing style (CPS). An instance of
See moreContinuation<R, A>
represents a computation resulting in anA
value. By passing a continuation of typeA -> R
, aContinuation<R, A>
will use theA
result and the continuation to produce anR
.Declaration
Swift
public struct Continuation<R, A>
-
A lazy, asynchronous, concurrent, and reusable stream of operations on elements of data.
let sum = (1..<50000).stream() .filter { $0 % 2 == 0 } .reduce(0, reducer: +) .wait()
This example makes a stream from the collection created by
(1..<50000)
. Then it filters out from that stream all elements that aren’t even. Then it reduces the stream; it starts at 0, then uses + to combine all elements. Streams work asynchronously, sowait()
is called to wait on the result.There are three stages to using streams.
Create the stream
Most often, a stream will be created using
CollectionType.stream()
, orStream.of(T...)
. But it is possible to create custom streams.A stream uses the
Continuation
monad to pass elements to handlers. A continuation is specialized to the typeContinuation<Future<()>, T>
. It returns aFuture<()>
because the handlers are asynchronous. The goal is to create a continuation that will call the handler once for each element. This is encouraged to be concurrent. A stream shouldn’t complete its future until all the futures returned by the handler are completed.The future’s type is
Future<()>
because it is isomorphic to()
. That is to say there is no actual return value. The only thing that matters is the time of completion.Manipulate the stream
There are several intermediary operations to manipulate the stream. The simplest is
Stream<T>.map(T -> U)
. This returns a stream that maps elements of the original stream to elements of a different type.Streams are immutable, lazy, and reusable, so intermediary operations aren’t changing the stream or its elements. Instead, they construct a new stream that will get its elements from the old stream, and modify them accordingly before passing the element to a handler.
Run the stream
Terminal operations on a stream will start running the stream. Most often, the stream will run asynchronously and concurrently, but this depends on the how the stream was created.
Running
a stream means to start accepting elements of the stream with a handler. The simplest example of this isStream.forEach
, which calls a handler for each element.Most streams are concurrent. This leads to dramatic performance improvements in many scenarios. There are, however, situations where the overhead of concurrency outweights the performance gains. For example, there are two different methods of reducing a stream. One is psuedo-serial, in that the reduction is performed serially, while the elements are computed concurrently. The other is fully concurrent, where both the computation of elements and the reduction are concurrent. For very fast reduction functions, the psuedo-serial method is usually faster, since there’s no concurrency overhead. For slower reduction functions, the concurrent method is usually faster, since more reductions can be occurring at a time.
Streams and collections differ in several ways. Besides being concurrent and asynchronous, streams are lazy and unordered. Streams do not store elements, and instead rely on abstract data sources. They can’t have their count calculated. Most importantly,
See moreStream
is not a data structure. It is a pipeline for manipulating elements, no matter how many there are. This is the inspiration for using theContinuation
monad.Declaration
Swift
public struct Stream<T>
-
A
See moreChannel
is a frontend for receiving values written to aChannelWriter
. AChannel
provides an interface for adding handlers for these values, as well as ways to create newChannels
that change values before passing them to handlers. Each handler is only ever called once; with the next value the channel receives after adding it.Declaration
Swift
public struct Channel<T>