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

Semantics of `fpsWhen` is quite off. #139

Closed
jvoigtlaender opened this Issue Jan 24, 2015 · 2 comments

Comments

Projects
None yet
2 participants
@jvoigtlaender
Contributor

jvoigtlaender commented Jan 24, 2015

So, in jvoigtlaender@274cd7e I had been trying (as part of the effort to make timestamp work in a consistent manner over all signals) to refactor fpsWhen while preserving its semantics. I failed in that, which luckily was caught by @jwmerrill, and fixed in elm-lang@6a36cd7. Throughout all this, I hadn't been checking what the semantics of fpsWhen was exactly, only trying to preserve it. Have now looked into the semantics of fpsWhen and found serious problems, I think.

Let's consider a signal isOn with the following behavior:

  • initial value False
  • at 0.5 seconds, has a True event
  • at 2.0 seconds, has a False event
  • at 3.5 seconds, has a True event
  • at 4.0 seconds, has a False event
  • no further events

For example,

isOn = flip member [1,7] <~ keepIf (flip member [1,4,7,8]) 0 (foldp (\_ c -> c+1) 0 (every 500))

Let's now consider the signal obtained by fpsWhen 1 isOn. Surprisingly, it shows the following behavior (since at least Elm-0.13):

  • at 0.5 seconds, has an event with value 0
  • at around 1.5 seconds, has an event with value 0
  • at 2.0 seconds, has an event with value 0
  • at around 2.5 seconds, has an event with value around 1000
  • at 3.5 seconds, has an event with (same) value around 1000
  • at 4.0 seconds, has an event with (same) value around 1000
  • at around 4.5 seconds, has an event with value 0
  • no further events

One way to check these claims is to run

main = asText << snd <~ foldp (\(t',d) (t,l) -> (t',(t'-t,d)::l)) (0,[]) (timestamp (fpsWhen 1 isOn))

(for example with different language versions at http://share-elm.com)

Many of the values we see appearing are dubious (for example, isOn was True only for 2 seconds overall, and yet we see three time deltas of 1 second each). Also, the events at 2.5 and 4.5 seconds shouldn't be happening at all. It turns out that the clearTimeout in the implementation of fpsWhen has no effect whatsoever. PR https://github.com/elm-lang/core/pull/138 fixes that specific "bogus events" aspect. However, even after that fix, the semantics of fpsWhen is still off. For the above situation it now behaves as follows:

  • at 0.5 seconds, has an event with value 0
  • at around 1.5 seconds, has an event with value 0
  • at 2.0 seconds, has an event with value 0
  • at 3.5 seconds, has an event with value 0
  • at 4.0 seconds, has an event with value 0
  • no further events

Now the bogus events at 2.5 and 4.5 seconds are gone, but all other events show value 0, even though the isOn signal has been True for 2 seconds, of which 1.5 seconds were consecutively, so certainly should have shown some non-0 time value passing, given that our granularity was "1 frame per second".

@jwmerrill

This comment has been minimized.

Show comment
Hide comment
@jwmerrill

jwmerrill Jan 24, 2015

Contributor

Good find. I think this points at the need to come up with some good way to
unit test Signal implementations. Especially for the Native code.

Some of these JS APIs make it very easy to make mistakes.
On Sat, Jan 24, 2015 at 11:23 AM Janis Voigtländer notifications@github.com
wrote:

So, in jvoigtlaender/core@274cd7e
jvoigtlaender@274cd7e
I had been trying (as part of the effort to make timestamp work in a
consistent manner over all signals) to refactor fpsWhen while preserving
its semantics. I failed in that, which luckily was caught by @jwmerrill
https://github.com/jwmerrill, and fixed in 6a36cd7
elm-lang@6a36cd7.
Throughout all this, I hadn't been checking what the semantics of fpsWhen
was exactly, only trying to preserve it. Have now looked into the semantics
of fpsWhen and found serious problems, I think.

Let's consider a signal isOn with the following behavior:

  • initial value False
  • at 0.5 seconds, has a True event
  • at 2.0 seconds, has a False event
  • at 3.5 seconds, has a True event
  • at 4.0 seconds, has a False event
  • no further events

For example,

isOn = flip member [1,7] <~ keepIf (flip member [1,4,7,8]) 0 (foldp (_ c -> c+1) 0 (every 500))

Let's now consider the signal obtained by fpsWhen 1 isOn. Surprisingly,
it shows the following behavior (since at least Elm-0.13):

  • at 0.5 seconds, has an event with value 0
  • at around 1.5 seconds, has an event with value 0
  • at 2.0 seconds, has an event with value 0
  • at around 2.5 seconds, has an event with value around 1000
  • at 3.5 seconds, has an event with (same) value around 1000
  • at 4.0 seconds, has an event with (same) value around 1000
  • at around 4.5 seconds, has an event with value 0
  • no further events

One way to check these claims is to run

main = asText << snd <~ foldp ((t',d) (t,l) -> (t',(t'-t,d)::l)) (0,[]) (timestamp (fpsWhen 1 isOn))

(for example with different language versions at http://share-elm.com)

Many of the values we see appearing are dubious (for example, isOn was
True only for 2 seconds overall, and yet we see three time deltas of 1
second each). Also, the events at 2.5 and 4.5 seconds shouldn't be
happening at all. It turns out that the clearTimeout in the
implementation of fpsWhen has no effect whatsoever. PR #138
https://github.com/elm-lang/core/pull/138 fixes that specific "bogus
events" aspect. However, even after that fix, the semantics of fpsWhen is
still off. For the above situation it now behaves as follows:

  • at 0.5 seconds, has an event with value 0
  • at around 1.5 seconds, has an event with value 0
  • at 2.0 seconds, has an event with value 0
  • at 3.5 seconds, has an event with value 0
  • at 4.0 seconds, has an event with value 0
  • no further events

Now the bogus events at 2.5 and 4.5 seconds are gone, but all other events
show value 0, even though the isOn signal has been True for 2 seconds, of
which 1.5 seconds were consecutively, so certainly should have shown some
non-0 time value passing, given that our granularity was "1 frame per
second".


Reply to this email directly or view it on GitHub
https://github.com/elm-lang/core/issues/139.

Contributor

jwmerrill commented Jan 24, 2015

Good find. I think this points at the need to come up with some good way to
unit test Signal implementations. Especially for the Native code.

Some of these JS APIs make it very easy to make mistakes.
On Sat, Jan 24, 2015 at 11:23 AM Janis Voigtländer notifications@github.com
wrote:

So, in jvoigtlaender/core@274cd7e
jvoigtlaender@274cd7e
I had been trying (as part of the effort to make timestamp work in a
consistent manner over all signals) to refactor fpsWhen while preserving
its semantics. I failed in that, which luckily was caught by @jwmerrill
https://github.com/jwmerrill, and fixed in 6a36cd7
elm-lang@6a36cd7.
Throughout all this, I hadn't been checking what the semantics of fpsWhen
was exactly, only trying to preserve it. Have now looked into the semantics
of fpsWhen and found serious problems, I think.

Let's consider a signal isOn with the following behavior:

  • initial value False
  • at 0.5 seconds, has a True event
  • at 2.0 seconds, has a False event
  • at 3.5 seconds, has a True event
  • at 4.0 seconds, has a False event
  • no further events

For example,

isOn = flip member [1,7] <~ keepIf (flip member [1,4,7,8]) 0 (foldp (_ c -> c+1) 0 (every 500))

Let's now consider the signal obtained by fpsWhen 1 isOn. Surprisingly,
it shows the following behavior (since at least Elm-0.13):

  • at 0.5 seconds, has an event with value 0
  • at around 1.5 seconds, has an event with value 0
  • at 2.0 seconds, has an event with value 0
  • at around 2.5 seconds, has an event with value around 1000
  • at 3.5 seconds, has an event with (same) value around 1000
  • at 4.0 seconds, has an event with (same) value around 1000
  • at around 4.5 seconds, has an event with value 0
  • no further events

One way to check these claims is to run

main = asText << snd <~ foldp ((t',d) (t,l) -> (t',(t'-t,d)::l)) (0,[]) (timestamp (fpsWhen 1 isOn))

(for example with different language versions at http://share-elm.com)

Many of the values we see appearing are dubious (for example, isOn was
True only for 2 seconds overall, and yet we see three time deltas of 1
second each). Also, the events at 2.5 and 4.5 seconds shouldn't be
happening at all. It turns out that the clearTimeout in the
implementation of fpsWhen has no effect whatsoever. PR #138
https://github.com/elm-lang/core/pull/138 fixes that specific "bogus
events" aspect. However, even after that fix, the semantics of fpsWhen is
still off. For the above situation it now behaves as follows:

  • at 0.5 seconds, has an event with value 0
  • at around 1.5 seconds, has an event with value 0
  • at 2.0 seconds, has an event with value 0
  • at 3.5 seconds, has an event with value 0
  • at 4.0 seconds, has an event with value 0
  • no further events

Now the bogus events at 2.5 and 4.5 seconds are gone, but all other events
show value 0, even though the isOn signal has been True for 2 seconds, of
which 1.5 seconds were consecutively, so certainly should have shown some
non-0 time value passing, given that our granularity was "1 frame per
second".


Reply to this email directly or view it on GitHub
https://github.com/elm-lang/core/issues/139.

@jvoigtlaender

This comment has been minimized.

Show comment
Hide comment
@jvoigtlaender

jvoigtlaender Jan 25, 2015

Contributor

I've fixed the fpsWhen function, using quite a bit reorganization, in PR https://github.com/elm-lang/core/pull/141.

For the scenario put up above, the behavior of fpsWhen 1 isOn is now as follows:

  • at 0.5 seconds, has an event with value 0
  • at around 1.5 seconds, has an event with value around 1000
  • at 2.0 seconds, has an event with value around 500
  • at 3.5 seconds, has an event with value 0
  • at 4.0 seconds, has an event with value around 500
  • no further events

Which is exactly the sane thing to do, I think. And makes the documentation of fpsWhen true, which says: "The first time delta after a pause is always zero, no matter how long the pause was. This way summing the deltas will actually give the amount of time that the output signal has been running."

Contributor

jvoigtlaender commented Jan 25, 2015

I've fixed the fpsWhen function, using quite a bit reorganization, in PR https://github.com/elm-lang/core/pull/141.

For the scenario put up above, the behavior of fpsWhen 1 isOn is now as follows:

  • at 0.5 seconds, has an event with value 0
  • at around 1.5 seconds, has an event with value around 1000
  • at 2.0 seconds, has an event with value around 500
  • at 3.5 seconds, has an event with value 0
  • at 4.0 seconds, has an event with value around 500
  • no further events

Which is exactly the sane thing to do, I think. And makes the documentation of fpsWhen true, which says: "The first time delta after a pause is always zero, no matter how long the pause was. This way summing the deltas will actually give the amount of time that the output signal has been running."

jwmerrill added a commit to jwmerrill/core that referenced this issue Jan 25, 2015

Fix semantics of fpsWhen.
fixes #139
alternative to #141

Semantics:

1. fpsWhen fires whenever isOn changes, or its internal setTimeout fires
2. Its value is a timedelta, which is 0 when isOn changes from false to true,
or is the time elapsed since the last event otherwise. As a corrolary, summing
deltas gives exactly the time between when isOn transitioned from false to
true, and when it transitioned from true to false.

@evancz evancz closed this in #143 Jan 26, 2015

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment