ds300 / derivablejs

Functional Reactive State for JavaScript and TypeScript
Apache License 2.0
515 stars 23 forks source link

Change reactor lifetime API to be more expressive #62

Open ds300 opened 7 years ago

ds300 commented 7 years ago

Reactors are like people—like almost any animal, actually—in that they are born and then they die, and during this time they can be either awake or asleep. When they are both alive and awake, they actively listen for and react to changes in their environment.

At the moment you define a birth date and death date for a reactor using the from and until conditions, and you define a wake time and sleep time using the when condition. These both declaratively describe spans of time, and yet they are not equivalent ways of doing that.

from and until are more flexible than when for delimiting a span of time. The reactor becomes alive as soon as the from condition is truthy. But then, no matter how the from condition changes, the reactor stays alive until the until condition becomes truthy (unless it was truthy to begin with, in which case the reactor never comes to life 😢 ).

To illustrate, if we were to use the when condition for defining life-spans, the reactor would come to life when the when condition becomes truthy and die when it becomes falsey.

i.e.

x.react(f, {when: alive}) // `when` controls the life-span, not the awake-span

is equivalent to

x.react(f, {from: alive, until: alive.not()})

But if we introduce independent variables for from and until:

x.react(f, {from: foo, until: bar});

How can we convert that back to a when condition? when: foo.and(bar.not()) doesn't work because foo could change back to a falsey value before bar becomes truthy. I think it is not possible to convert, because there is another implicit piece of atomic state which you cannot reference: the state of whether foo has been truthy since the reactor was defined.

So {from: foo, until: bar} is really equivalent to {when: fooHasBeenTruthyAtSomePoint.and(bar.not())}


So I think it should be possible to describe life-spans with a when condition, because sometimes that's convenient, and awake-spans with from and until conditions, because sometimes that's convenient.

Here's the canonical format I propose:

blah.react(f, {
  alive: {
    from: foo,
    until: bar,
  },
  awake: {
    from: baz,
    until: quux,
  },
})

But when will be allowed in the alive or awake time span descriptors, as a replacement for the from and until conditions.

blah.react(f, {alive: {when: foo}})

// or, as a shorthand:

blah.react(f, {alive: foo})

So far I haven't mentioned skipFirst and once from the existing scheme. Still thinking about that.

oskbor commented 7 years ago

sounds good, but I will unfortunately not have time to work on this

ds300 commented 7 years ago

That's cool, huge thanks for all your work up to now! :1st_place_medal:

ds300 commented 7 years ago

Hmmmm... thinking about this some more I'm not sure it makes sense to allow from and until in awake coniditons because of cyclical situations that don't make sense like, if from becomes truthy and then until becomes truthy, but from is still truthy, so does the reactor wake up again instantly? Anything else would be counter-intuitive, but then until is still truthy and the reactor should go to sleep again instantly. And so on and so on.

This can't happen with the alive condition because death is final.

So now I'm thinking the alive condition should be programmable with a when clause or from and until clauses, but the awake condition should only be programmable with when.