Skip to content

Feature suggestion: reactive aggregate r-var #15

@lrq3000

Description

@lrq3000

I'm not sure if this is the correct repo to post this feature request, I'm sorry if it's not, I can repost elsewhere if necessary.

First off, I am very impressed at the concise expressivity iooxa offers in terms of reactive components, it's very easy and reliable once you understand how it's meant to be used! Awesome work, it's even better than the already great ink-components predecessor package :-D

I would like to suggest an extension on how variables are handled to make some of them reactive, just like r-display for example. This would be especially useful for variables that are defined as an aggregate of other variables, in other words are dependent on other variables, which is often useful for complex calculations to store and reuse intermediate results.

Currently, the only way to propagate changes is "top-down": the parent variables must update the children's variables values using the :change attribute. But this leads to an unnatural writing style in complex applications. This could be improved with a "bottom-up" approach where we directly define inside the children's variables definition what are the parent/dependent variables, similarly to what is done with r-display.

Let me show an example to clarify: let's say we make an app with 5 r-vars: a, b, c, the sum of a+b+c and the sum squared.

Here is what we currently need to write:

<script src="https://unpkg.com/@iooxa/article"></script>
<link rel="stylesheet" href="https://unpkg.com/@iooxa/article/dist/iooxa.css">

<div style="display: flex; flex-direction: column">
<r-var name="a" value="1" type="Number"></r-var>
<r-var name="b" value="10" type="Number"></r-var>
<r-var name="c" value="100" type="Number"></r-var>
<r-var name="sum" value="111" type="Number"></r-var>
<r-var name="sum_squared" value="12321" type="Number"></r-var>

<r-input label="a" :value="a" :change="{a: parseFloat(value), sum: parseFloat(value)+b+c, sum_squared: (parseFloat(value)+b+c)**2}"></r-input>
<r-input label="b" :value="b" :change="{b: parseFloat(value), sum: a+parseFloat(value)+c, sum_squared: (a+parseFloat(value)+c)**2}"></r-input>
<r-input label="c" :value="c" :change="{c: parseFloat(value), sum: a+b+parseFloat(value), sum_squared: (a+b+parseFloat(value))**2}"></r-input>
<r-input label="Sum" :value="sum" :change="{sum: value, a: value-b-c, sum_squared: value**2}"></r-input> <!-- Simply change one variable so that the input sum is correct -->
<r-input label="Sum squared" bind="sum_squared"></r-input>
</div>

Beside the issues with parseFloat which I mentioned in another issue elsewhere, the current approach leads to a lot of redundancy. Basically, the children r-var sum needs to be redefined inside each parent r-var's :change attribute, with pretty much the same code copy/pasted except that the current parent variable becomes value. Not only that, but all children of sum itself, such as sum_squared here, also need to be redefined at the level of each parents at all levels, hence here in both a, b, c and sum. We have only a 3 level depth tree here, the more depth the more redundancy.

Here is my "bottom-up" suggestion:

<script src="https://unpkg.com/@iooxa/article"></script>
<link rel="stylesheet" href="https://unpkg.com/@iooxa/article/dist/iooxa.css">

<div style="display: flex; flex-direction: column">
<r-var name="a" value="1" type="Number"></r-var>
<r-var name="b" value="10" type="Number"></r-var>
<r-var name="c" value="100" type="Number"></r-var>
<r-var name="sum" value="a+b+c" type="Number"></r-var>
<r-var name="sum_squared" value="sum**2" type="Number"></r-var>

<r-input label="a" :value="a" :change="{a: parseFloat(value)}"></r-input>
<r-input label="b" :value="b" :change="{b: parseFloat(value)}"></r-input>
<r-input label="c" :value="c" :change="{c: parseFloat(value)}"></r-input>
<r-input label="Sum" :value="sum" :change="{a: value-b-c}"></r-input> <!-- Simply change one variable so that the input sum is correct -->
<r-input label="Sum squared" bind="sum_squared"></r-input>
</div>

As you can see now the aggregate variables, sum and sum_squared, are now defined directly in r-var. The idea is that the value of sum should always reflect a+b+c. Hence in the :change attribute of the r-input of sum, we do not need to update the value of sum as it is always defined by the sum of a+b+c, we only need to change one of these parent variables (here I chose a for the sake of this example). Also I guess that in the runtime, if we make something reactive, it's then tied to these variables, so I guess that trying to set {sum: value} wouldn't work anyway.

So to summarize, sum and sum_squared are in this case a bit different variables than the standard r-var: whereas the r-var are meant to be directly set by user inputs, sum and sum_squared are only indirectly set, and are directly tied to other r-vars. That's why I call them "aggregate variables". In the above example I used :value="a+b+c" to define the dependency to the parents, but if it's simpler from an implementation standpoint to use a differently named attribute I see no issue.

About potential issues: it seems infinite recursion may be possible when updating reactive aggregate variables. In the example above, updating r-var a should update sum but then in cascade it should also update sum_squared. We could imagine two aggregates variables like: <r-var name="b1" value="a*b2"></r-var> and <r-var name="b2" value="a*b1"></r-var> with r-var a being a standard r-var that can be set with a r-input for example. Updating a would trigger an infinite loop between b1 and b2 in theory. I'm honestly not very experienced with javascript so I don't know how reactivity works technically, so I'm not sure how this could be handled, maybe with a list of variables to update to ensure the cascading only updates each children variable once when a is updated? Or just throw an error, that would work too.

Maybe this is not possible to implement, so in that case please disregard this ticket :-) It just spawned from my experiments to see how far iooxa could go, and as I found it can be used to make not just explorable explorations but even calculators and probably more when combined with custom javascript (or Brython) :-) The root of my thought started as very simple question, I was just wondering why I could not store a r-var for an intermediate result just like values of r-display while keeping the reactivity. In the end, as you can see in the concrete case that is the calculator I linked above, I used a mix of the first approach of duplicating code in all parent r-vars' inputs, and of avoiding the declaration of unnecessary intermediate resultst r-vars to minimize the depth of the dependency tree.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions