Reactive Programming
What is Reactive Programming?
In simplest terms, reactive programming is a different way of conceptualizing systems. Consider an updating system, like a user interface (UI). Initially, your UI is in a specific state. When a user interacts, say by clicking or modifying something, the UI needs to reflect these changes. It updates.
Traditionally, programmers would write logic manually to update the state. But that’s a recipe for a disaster. If there’s a mistake in the logic, it is extremely hard to debug, because the multitude of potential states resulting from every user interaction is overwhelming. Nobody has the brain to hold so much state in their head.
Enter Reactive Programming: freezing time
Rather than manually handling these updates, reactive programming encourages you to write your logic as if time was frozen. From the programmer’s perspective, it’s like pretending the application is always initializing. The real magic happens when a reactive programming framework takes over: it manages updates for you! When there’s a change, the framework reruns the logic incrementally, efficiently managing the updates without starting from scratch.
Benefits? No explosion of possible states. Your job is to worry about what the logic should do at initialization time, while updates are handled for you. Sounds too good to be true? Well … there are a few conditions for this to work.
Immutability
One of the most important aspect of reactive programming is using immutable objects. Immutable objects have two important properties:
-
They can be stored and loaded in a cache without a copy. As you can imagine, reactive programming will require some caching. How else are we going to incrementally update if not by keeping around the objects that do not change? For caches to be efficient, you will have to store immutable objects in them. Why? Because we don’t want to have to pay for a copy every time we retrieve an object out of a cache. That would be too inefficient.
-
Older versions of objects can be kept around cheaply, allowing to compare old versions with the new one. As stated before, a reactive framework needs to incrementally recompute the state of the application. For that to be efficient, the framework needs to determine which parts of the state have changed. Immutable objects are perfectly suited for that.
The best way to make sure the objects you need are immutable is through a type-system. That’s why, at SkipLabs, all of our reactive programs are written in SKIP. SKIP is programming language has been specifically created to provide all the mutability guarantees required in a reactive system. In SKIP, immutable is the default, mutable the exception. Moreover, immutability is transitive: for an object to be immutable the transitive closure of the pointers present in the object needs to be immutable.
Managing dependencies
For a reactive framework to partially recompute the state of a program, the code must be structured accordingly. It means carefully using loops or other constructions that create dependencies. That’s because you could end up recomputing a lot more than you intended to when an update occurs. Instead, specific data structures should be employed, at least when many objects are involved (small loops don’t matter).
SKStore
At SkipLabs, we developed a Framework called SKStore
to structure
reactive programs easily. SKStore
is a reactive key/value store. It
let’s you define relationships between directories, forming a graph of
computation. In the following snippet of code we associate a directory
called “added1” to an existing directory that was containing integers.
dirOfInts.map(
SKStore.IID.keyType, // The type of the key
SKStore.IntFile.type, // The type of the Files
context,
SKStore.DirName::create("/add1/"),
(context, writer, key, values) ~> {
for(value in values) {
writer.write(key, value + 1)
}
}
)
The directory /add1/
is now bound to dirOfInts
, every time one
changes, the other will follow. You can lookup values in any directory
you want, perform all the typical operations you normally would on a
key/value store, and everything will be tracked, in other words, the
system will know how to recompute things incrementally when a change
occured.
Wonder where the magic is? The magic is in the type of closure we
used. Normal closures use a different syntax ->
, but in this case we
used a pure closure ~>
. Pure closure guarantee that no effect can
escape the scope of the closure. Meaning, nothing that is captured by
the closure can mutate. However, it does not mean that the closure has
no effect, it could mutate parameters that have been declared as
mutable. In this case, the context is actually mutable, but that’s ok
because the context is specifically designed to track operations
affecting the state of the store.
Examples outside of Skip
There are many places where reactive programming has been successful.
-
Spreadsheets: Believe it or not, spreadsheets are a classic example of reactive programming. The long-lasting objects, the cells in spreadsheets, update and react based on changes in values.
-
ReactJS: A renowned JavaScript framework, React operates on the principles of reactive programming. Developers model the UI as if it’s static, but when changes are required, immutable properties are used to determine the necessary recalculations.
The Future: Expanding Horizons with Reactive Programming
At Skiplabs, the potential of reactive programming is being further tapped into. Take ‘SKDB,’ a reactive SQL database. But the vision goes beyond this. Imagine applying reactive programming to distributed systems. These systems are essentially vast programs, their states scattered across multiple machines. Reactive programming could abstract the complexities of the network, from latency to potential race conditions, offering a more streamlined and efficient approach.
In conclusion, reactive programming offers a fresh perspective on managing and updating systems. It’s a paradigm shift that has profound implications for the future of coding and system management.