Make reactive declarations part of the language

One of the big pains of programming is to keep track of what happens in what order. When you get a back, you need to step into the code and investigate what happened in a certain case. With reactive programming this problem is eliminated. An example is a spreadsheet. A1 = B2 * 2, means that A1 always will be equal to B2 * 2. When B2 changes, A1 changes too. We have got many nice web frameworks, like React, Vue and Svelte, that also work this way:

Here is an example of reactive declarations in Svelte:
https://svelte.dev/tutorial/reactive-declarations

$: doubled = count * 2;

doubled will always be equal to count * 2, except the time it takes to calculate. Svelte makes its own way to express this since it’s not part of JavaScript. But shouldn’t these kind of reactive fields (or whatever we should call them) be part of the language? This would be a great feature for front-end development in Kotlin.

It could be something like:

var count = 1

reactive doubled = count * 2
reactive quadrupled = doubled * 2

Then buttons, text areas etc could be bound to reactives or vars. Much of the logic could be shared across multiple platforms, like web, iOS, Android etc.

There should also be possible to do reactive if-statements, as it’s done in Svelte. Could look something like:

reactive if (count >= 10) {
	alert("count is dangerously high!")
	count = 9
}

What do you think?

Isn’t that the same as a property with a getter?

var count = 1
val doubled get() = count * 2

Or am I missing something?

4 Likes

Exactly, or even a delegate if you’re feeling like it.

2 Likes

Using a property with a getter causes the computation to happen when the result is retrieved. It seems to me as thought the original question is about having the results computed every time the input have changed.

I think what the original question was getting at is what is usually called “declarative programming”. This kind of programming can be very neat, but tacking it onto an otherwise imperative programming language is not ideal in my opinion. Instead, it may be useful to take a look at one of the languages which are built around this concept, like Prolog.

Here is some more information on this topic: Declarative programming - Wikipedia

1 Like

Yeah I did realise that that’s probably an interesting caveat, but like assuming that the computation is pure, which it probably should be imo, then there’s observabely no difference between calculating it when the value changes and calculating it when it gets called. If you wanted to, you can use an observable delegate to achieve that behaviour, but there’s basically no way to do it for properties that you can’t control and so including it in the language basically wouldn’t work unless it gets computed when called, which is what we have already in the form of delegates/getters.

2 Likes

I do like how the problem is described: keeping track of what your code does and in what order can be a challenge.

This proposal is like a language built-in listener/call-back system. One field update would cause many listeners to fire. Which order would they fire in? How would you stop a recursive update from happening? How could one keep track of the order of events?

If this was added, tracing code for a normal property would become as difficult as an Observable property. For example, you wouldn’t be able to simply read B1’s setter to understand your code. Instead, you would have to find and read through every callback (reactive property) attached to B1 and find and read every callback to those properties.

2 Likes

I’m not sure about the ordering of the firing, but I don’t think it matters. It would be exactly like a spreadsheet, where the value always will be equal the result of the formula, so not sure if the ordering would matter. Recursive updates would be found at compile-time, and produce an error, like in a spreadsheet.

1 Like

To your point, the recursive setters/getters could happen with normal properties too. :slight_smile:

The reason I brought it up is mainly because debugging when recursion happens would be harder since I wouldn’t be able to look at a property and know it’s safe.
For example: if I have a property with no gett/setter, var name = "Bob", I know it’s safe and what code will be fine since users can’t add listeners later on.

We do have this reactive behavior used in libraries like React. That might be a better example than spreadsheets since it’s programming related.

2 Likes

Yes! React, Vue and Svelte all have this behavior, and I think it somehow should be part of the language, not needed to be added by a library. Spreadsheets is very programming related, it’s all reactive programming, like the mentioned web frameworks.

Jetpack Compose already does this without introducing changes to the language (bar some compile-time transformations), I’m not sure what you’d gain from bloating the language with a such a niche feature when it can be already done as-is.

1 Like

Ah, gotcha. Well, maybe there’s a way to make a general-purpose library of delegates that allows easy chaining of values that is separate from any UI framework (something more like your examples and less like ReactiveX/Project Reactor/etc).

I’ve been messing with delegates on something unrelated recently–maybe later on I can add an example of what a library solution could potentially look like.

EDIT:
I’m imagining something like:

var a by reactive(256)
var b by reactive { on(B1) * 2 }
var c by reactive { on(B1) / }

var reactiveWrapper by reactiveOf(Other::someProp)
3 Likes

Yes, but what about something easier to recognize (compared to normal value declaration) like:
reactive val doubled = count * 2
or even
reactive val doubled → count * 2
(like in lambdas)?

Kotlin’s design generally follows the idea of adding few but powerful language systems that allow to create features. 2 great examples for this are inline functions and delegates.
Your idea goes the other way, it provides an alternate syntax (that isn’t even much better if it is better at all) and adds nothing to the language that can’t already be done with delegates or simple getters.
Why is

reactive val double = count * 2

better than

val double by reactive { count * 2 }

The second syntax would need some sort of library to support this but as we see in the projects mentioned above those functionality already exists. Your idea while it sounds nice just makes the language more complex (which makes it harder to learn and slows down development) without adding anything substantial.
While it’s not officially state that kotlin follows the minus 100 point rule when it comes to new features, it is something that is often used by the community to assess a new feature to the language.

5 Likes

The idea with reactive is interesting, however as others already said, I think a library would be a better fit. One such library could be Flow that is based on coroutines. But Flow is missing a lot of functionality to be a powerful tool. It is more like a foundation for another powerful library. The set of Flow operators is very limited compared to reactive libraries like Reactor or RxJava. Especially Reactor works very well with Kotlin, btw. It has a steep learning curve but is a really powerful tool.

Do we really need another keyword?


fun main() {
    var n = 1
    val double by { n * 2 }
    println("$n -> $double")
    n = 2
    println("$n -> $double")
    n = double
    println("$n -> $double")
}

public inline operator fun <T> (() -> T).getValue(thisRef: Any?, property: kotlin.reflect.KProperty<*>): T = invoke()

maybe we need a proper library.

1 Like

Isn’t that just a normal getter but with more steps?

I think the main point of OP is that instead of having values update when they’re are accessed, he wants to update the values when their upstream values are updated.

2 Likes

You should take a look to the MutableStateFlow.

Yes, it is more complex than this proposal, but this task isn’t a trivial issue (as supposed).

1 Like

That’s nothing new, either use a function like suggested before, or implement an observable pattern Observer pattern - Wikipedia

Because that’s what this pattern is for, react to a change in a variable.

2 Likes