Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recommended way to deal with "Runaway clock detected"? #29

Open
trusktr opened this issue Jul 23, 2019 · 1 comment
Open

Recommended way to deal with "Runaway clock detected"? #29

trusktr opened this issue Jul 23, 2019 · 1 comment

Comments

@trusktr
Copy link

trusktr commented Jul 23, 2019

Here's a small recursive example:

const word = S.data('John')
const number = S.data(123)

S.root(() => {
    S(() => {
        if (word() == 'mary') {
            number(number() + 1)
        }
    })  

    S(() => {
        console.log(number())
    })
})


word('blah')
word('mary')
word('foo')

It causes the first computation to run infinitely (until the error) because it reads and sets the number in the same computation.

What's you advice for these sorts of situations in general?

In this case, maybe the hypothetical user meant:

const word = S.data('John')
const number = S.data(123)

S.root(() => {
    S(() => {
        console.log(number())
    })
})

S.on(word, () => {
    if (word() === 'mary') {
        number(number() + 1)
    }
})

word('blah')
word('mary') // logs the new number
word('foo')

Curious to know in what sorts of cases we might run into the infinite recursion, and how to deal with it.

@adamhaile
Copy link
Owner

Yeah, your second example is exactly what I'd recommend.

I tell people that there are two kinds of scenarios where we might want to set signals inside computations. One is event-based, the other is rule-based.

We can tell event-based ones, because if we describe what we want to do, our sentence usually starts with "when X changes ...." In your example above, we might describe it as "when word() changes to "mary", increment number()".

S.on(...) is built to handle exactly these use cases, event-based logic. So here, your second take is spot on.

We can tell rule-based ones, because our sentence usually starts with "when X is ...." S() is appropriate for rule-based scenarios, but we need to be careful that our predicate, the "X is" part, defines a condition for when the changes should stop.

For instance, your original code example could read "when word() is "mary", number() should be 1 more than number()." That's an impossible rule to satisfy: there's no value of number() that is one more than number(). S starts incrementing number(), looking for a solution, but never finds it, until the runaway clock watchdog kills the update.

On the other hand, this would work:

    S(() => {
        if (word() == 'mary' && number() < 4) {
            number(number() + 1)
        }
    }) 

We could read this as "when word() is "mary" and number() is less than 4, number() should be one more than number()." (More idiomatically, we might say "number() should start counting up.")

When this runs, S again starts incrementing number(), but now it does find a solution, once number() gets to 4.

Make sense?

In truth, there's a third case, more common than rule-based scenarios. That's where they've started writing a data signal with a setter computation, when their intent would be clearer with a simple computation, or even just a lambda. If the description isn't "when X is ..." but just "X is," then that's usually the case.

So say the description was "number() is the length of word(), and they'd written that as:

const number = S.data(0);
S(() => number(word().length));

That would be better and clearer as:

const number = S(() => word().length)
// or even just ...
const number = () => word().length

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants