The Heart of Reactive Extensions for .NET
Rx.NET
Reactive Extensions for .NET (Rx.NET) is profoundly useful, and developers often end up loving. Why is that? Endjin Technical Fellow Ian Griffiths explains how Rx.NET's deep roots in mathematical foundations make it both powerful and beautiful.
To learn more about Rx.NET, check out the free book Introduction to Rx.NET 2nd Edition (2024).
Transcript
Ian Griffiths
Hi, I'm Ian Griffiths, Technical Fellow at endjin. Back at the start of 2023, endjin took over as primary maintainers of the Reactive Extensions for .NET, usually known as Rx.NET, an open source .NET project for event driven programming. In this video, I want to talk about how I see the heart of Rx.
Rx does something absolutely vital. It's useful in any program where things happen. Of course, people managed to write programs where things happened back before Rx was invented in around 2008. Most user interface technologies use some sort of message loop. Some APIs accept function pointers so they can call you back when something happens. But Rx got to the heart of the problem in a way that these ad hoc approaches never did. The Rx model has been widely adopted outside of its origins at Microsoft, and there's a good reason for that. It's a subtle reason, and at first glance it may seem so simple that you might think there's nothing to it, but once people get their heads around Rx they often come to really love it. And there's a deep reason for this. It all boils down to this.
Rx embraces Mathematics. The abstraction at its core is based on fundamental elements of computation, and all the standard Rx operators were designed to align with the principles of function composition. Components built with Rx compose so naturally that it's very easy to take for granted.
We might well believe that this was inevitable, and that any attempt to represent events would work equally well. But this is not so. It's no happy accident that Rx works so well. It's a side effect of its basic mathematical soundness. So although I could point at all the functionality available in the Rx.NET libraries, or I could show the elegance with which it can solve many problems, I would just be showing you the fruits of Rx's design. And these are great reasons to use Rx, but this misses the point of why Rx is great. To understand that, you really need to get back to basics. Rx's inventors asked themselves what events look like at a mathematical level.
That might sound very dry and academic, but I think they discovered a deep truth. And the beauty of this is what draws people to Rx. If we strip away all technological specifics, and look at the fundamental essence of computation, what would events look like? Rx's inventors used Lambda Calculus, a formal mathematical system powerful enough to embody universal computation.
The model these people came up with is almost comically simple. In the Lambda Calculus world, a source of events is a function which accepts another function as input. Now, anyone who has used an API that works with function pointers might observe that this is hardly a new idea. However, the Rx designers made a critical observation.
When you reduce it to this elemental form, the Mathematics is entirely reversible, and this reveals a direct relationship between sequences of events and lists. On the one hand, this seems like a pretty obvious statement. A stream of events is just one thing after another, and so's a list. But there's a difference between a vague, hand wavy assertion of that fact and a rigorous mathematical proof.
Function pointers are the Wild West. Pointers are the raw language of the machine, and handling machinery directly is dangerous. That's why security bugs continue to be commonplace in operating systems that are decades old. By stripping away all the accidental complexity of any particular processor architecture and getting down to the elements of computation, we avoid the associated security problems, but we can also see the basic essence.
Programmers are used to having lists. More or less any programming language you're likely to use will have arrays or lists or sequences or some similar concept, and there's not a lot of variation in how languages present this. But for most of the history of computing, There has been less agreement on how sources of events should be modelled.
Many languages have no inherent way to represent this, and even those that do, don't all agree on how to do it. Rx's most important contribution is, in my view, that it establishes a common abstraction for sources of events. Because the basic model is rooted in Mathematics, it is sound. The principles of functional composition are deeply embedded in the design of Rx's operators, and this is the key to its success.
The essence of Rx is its abstractions. In Rx.NET That means the IObserver<T>
and IObservable<T>
interfaces, and the set of standard link operators. These directly embody the mathematical insights that went into Rx's design, and those insights are the heart of Rx.