Continuations as collections

Creating a collection library for continuations

Wim Jongeneel
ITNEXT
Published in
4 min readNov 1, 2020

--

Last week I published an article on continuations in TypeScript. In that article, I mentioned that continuations (unlike promises) act like collections of values that start existing over time. In this article I want to explore this further and see how much your traditional array has in common with continuations.

The big limiting factor when implementing a collection library for continuations is that we don’t know the values the continuation is going to output or even how many. As a result of this not all of the methods that are widely accepted to be standard collection operators can be implemented for continuations. However, the asynchronous nature of continuations also allows for new unique and interesting methods to be defined.

Quick recap

A continuation is an abstraction of something that produces values over time. In essence, it is nothing more than ‘a thing’ that you give a callback and it will call that callback whenever it some value. This means that a continuation is pretty similar to a promise, with the main exception being that a promise always only produces one value whereas a continuation can produce many values.

An example of a keyboard continuation

By modeling this concept of an eventlistener (of callback) as a data structure that contains values we gain the ability to add methods to it. The most important of those a map to transform a continuation from one type to a continuation of another type and then to chain multiple continuations together into one bigger continuation.

Example usage of keyboard, map and then
The interface of a continuation

Collection combinators for continuations

There are multiple ways to combine two collections into one. The simplest one is to make a collections with all the elements of both collections. Usually called concat, this operations gives you first all the elements of a and then all the elements of b. We can make a function that takes two continuations and makes a single continuations that will output all the values of those. Only the ordering will be in the order they are outputted instead of all the a’s first and then the b’s. Because this slight difference I have decided to call it merge instead on concat to avoid confusion.

The intersect operator is our next operator. This function makes sure that the resulting collection only contains those values that are present in both the inputs. We can implement this for continuations by keeping track of the outputs and only outputting a value if the other continuation already has outputted this value. Note that this function only outputs values that already have been outputted and doesn’t output a value when a output occur that still has to (but will) be outputted by the other continuation.

except outputs only those values that the other continuation hasn’t (yet) outputted. Its implementation looks very similar to that of intersect, whit the only difference being the inverted condition. Note that this one only prevents outputs of values that have already been outputted by the other continuation, not of values that in the other continuation will output in the feature.

Another common combinator of collections in functional programming is the zip function. zip takes combines two collections into a collection of pairs. This function can also be implemented for continuations:

Subcollections with continuations

We can also create continuations that contain a subset of the values of the original continuations. The most common of such functions are filter, skip and take, all of which where already shown in the earlier article.

filter and skip for continuations

With similar constructs as in the examples above we can implement many more selection methods like take_while, take_until, skip_while and skip_until. first can be created with take(1), as a continuation that will only output once is of the exact same type as a continuation that will output many times. We cannot implement last, take_last or any other selections that works from the back of a collection for continuations as we can never know if a value is the last one that will ever be outputted.

distinct is another operator that will give you a subcollection that doesn’t require a predicate. distinct can (among other things) be used to give the intersect and except functions a more set-theory style behaviour (being to never output a value more than one time).

Arregrations for continuations

The final property of collections we will look to is their ability to be used in analytic functions. Because of the fact that continuations as asynchronous we cannot just calculate averages or sums over all the values they will (eventually) contain. One thing we could do is turn a continuation of number into a continuation that outputs new values for the arregration. See for example the sum function below. Every time the c outputs the new continuation will output the new sum of all values c has outputted.

A cont that outputs a new value for the sum every time the inner cont outputs

A different way to analyse a collection of numbers is to use a rolling arregration. A rolling arregration is an arregration over the last n values from that value. This idea works really well with an asynchronous populated collection as we can output the next value of the rolling arregration when the inner continuation outputs a new value.

A rolling average with a dynamic step

Conclusion

As you can see, continuation can be seen as collections including all the interesting stuff we usually do with them. Reasoning and implementing those methods for continuations is a great way to improve your understanding of them beyond the basics.

--

--

Writer for

Software Engineer at Mendix (Rotterdam, The Netherlands) • Student MSc Software Engineering • Functional programming enthusiast