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

Simplify initClock? #304

Open
turion opened this issue Mar 29, 2024 · 5 comments · May be fixed by #323
Open

Simplify initClock? #304

turion opened this issue Mar 29, 2024 · 5 comments · May be fixed by #323
Milestone

Comments

@turion
Copy link
Owner

turion commented Mar 29, 2024

Since long, I wanted to simplify the type signature of initClock from

  initClock ::
    cl ->
    m (MSF m () (Time cl, Tag cl), Time cl)

to

  initClock ::
    cl -> MSF m () (Time cl, Tag cl)

There are several reasons for the more complicated type signature:

  • The extra m action can be used to initialise a resource. (Although this is usually the least concern, the initialization could also happen upon the first tick, or be done with a ReaderT newtype.)
  • The m action is used to generate an initial time. This has several advantages:
    • It is simpler to refer to the time since clock initialization.
    • If the initial time was the first timestamp, then the first time difference would always be 0. With a dedicated initial time, we can often have an interval > 0 (although not always, e.g. in pure clocks).
    • Sometimes we have no good tag value at the initialization, and one would have to artificially add one (or wrap the tag in a Maybe)

But there are also downsides to this complicated type signature:

  • It's complicated to explain and understand. It's extra boiler plate when writing a new clock, you now have to measure the time in the running clock and also at initialization. Overall, it's not conceptually nice.
  • We always have to throw away one initial time when scheduling several clocks. This is an arbitrary choice which can lead to imprecision. If initClock directly returns a running clock, we could simply merge the timestamp streams.

With #299 (automata) there would be a new downside: The state of the running clock is typically not known statically (because it is hidden behind a monadic action), therefore GHC cannot optimize the whole Rhine further after clock erasure. This causes a performance degradation that is not easily justifiable.

Overall I believe that after #299, a serious attempt at simplification should be made.

@turion turion added this to the v2 milestone Mar 29, 2024
turion added a commit that referenced this issue Apr 19, 2024
… through strictness

* Maybe through simplifying initClock (#304)
* Looking at the Core it turns out that erased clock isn't completely simplified,
  and it's somehow obvious because it can't be inlined since it's recursive
* I was hoping that if the automaton is evaluated strictly enough, it would be reduced to WHNF before reactimation starts
  but it's unclear whether this would even be visible in Core
turion added a commit that referenced this issue Apr 22, 2024
… through strictness

* Maybe through simplifying initClock (#304)
* Looking at the Core it turns out that erased clock isn't completely simplified,
  and it's somehow obvious because it can't be inlined since it's recursive
* I was hoping that if the automaton is evaluated strictly enough, it would be reduced to WHNF before reactimation starts
  but it's unclear whether this would even be visible in Core
turion added a commit that referenced this issue May 3, 2024
… through strictness

* Maybe through simplifying initClock (#304)
* Looking at the Core it turns out that erased clock isn't completely simplified,
  and it's somehow obvious because it can't be inlined since it's recursive
* I was hoping that if the automaton is evaluated strictly enough, it would be reduced to WHNF before reactimation starts
  but it's unclear whether this would even be visible in Core
turion added a commit that referenced this issue May 3, 2024
… through strictness

* Maybe through simplifying initClock (#304)
* Looking at the Core it turns out that erased clock isn't completely simplified,
  and it's somehow obvious because it can't be inlined since it's recursive
* I was hoping that if the automaton is evaluated strictly enough, it would be reduced to WHNF before reactimation starts
  but it's unclear whether this would even be visible in Core
turion added a commit that referenced this issue May 3, 2024
… through strictness

* Maybe through simplifying initClock (#304)
* Looking at the Core it turns out that erased clock isn't completely simplified,
  and it's somehow obvious because it can't be inlined since it's recursive
* I was hoping that if the automaton is evaluated strictly enough, it would be reduced to WHNF before reactimation starts
  but it's unclear whether this would even be visible in Core
turion added a commit that referenced this issue May 3, 2024
… through strictness

* Maybe through simplifying initClock (#304)
* Looking at the Core it turns out that erased clock isn't completely simplified,
  and it's somehow obvious because it can't be inlined since it's recursive
* I was hoping that if the automaton is evaluated strictly enough, it would be reduced to WHNF before reactimation starts
  but it's unclear whether this would even be visible in Core
turion added a commit that referenced this issue May 7, 2024
… through strictness

* Maybe through simplifying initClock (#304)
* Looking at the Core it turns out that erased clock isn't completely simplified,
  and it's somehow obvious because it can't be inlined since it's recursive
* I was hoping that if the automaton is evaluated strictly enough, it would be reduced to WHNF before reactimation starts
  but it's unclear whether this would even be visible in Core
turion added a commit that referenced this issue May 7, 2024
… through strictness

* Maybe through simplifying initClock (#304)
* Looking at the Core it turns out that erased clock isn't completely simplified,
  and it's somehow obvious because it can't be inlined since it's recursive
* I was hoping that if the automaton is evaluated strictly enough, it would be reduced to WHNF before reactimation starts
  but it's unclear whether this would even be visible in Core
turion added a commit that referenced this issue May 7, 2024
… through strictness

* Maybe through simplifying initClock (#304)
* Looking at the Core it turns out that erased clock isn't completely simplified,
  and it's somehow obvious because it can't be inlined since it's recursive
* I was hoping that if the automaton is evaluated strictly enough, it would be reduced to WHNF before reactimation starts
  but it's unclear whether this would even be visible in Core
turion added a commit that referenced this issue May 8, 2024
… through strictness

* Maybe through simplifying initClock (#304)
* Looking at the Core it turns out that erased clock isn't completely simplified,
  and it's somehow obvious because it can't be inlined since it's recursive
* I was hoping that if the automaton is evaluated strictly enough, it would be reduced to WHNF before reactimation starts
  but it's unclear whether this would even be visible in Core
turion added a commit that referenced this issue May 10, 2024
… through strictness

* Maybe through simplifying initClock (#304)
* Looking at the Core it turns out that erased clock isn't completely simplified,
  and it's somehow obvious because it can't be inlined since it's recursive
* I was hoping that if the automaton is evaluated strictly enough, it would be reduced to WHNF before reactimation starts
  but it's unclear whether this would even be visible in Core
turion added a commit that referenced this issue May 10, 2024
… through strictness

* Maybe through simplifying initClock (#304)
* Looking at the Core it turns out that erased clock isn't completely simplified,
  and it's somehow obvious because it can't be inlined since it's recursive
* I was hoping that if the automaton is evaluated strictly enough, it would be reduced to WHNF before reactimation starts
  but it's unclear whether this would even be visible in Core
turion added a commit that referenced this issue May 10, 2024
… through strictness

* Maybe through simplifying initClock (#304)
* Looking at the Core it turns out that erased clock isn't completely simplified,
  and it's somehow obvious because it can't be inlined since it's recursive
* I was hoping that if the automaton is evaluated strictly enough, it would be reduced to WHNF before reactimation starts
  but it's unclear whether this would even be visible in Core
@turion
Copy link
Owner Author

turion commented May 16, 2024

The extra m action can be used to initialise a resource.

That's actually the wrong approach: The clock value should already contain the initialised resource! So if anything, there should be an action m cl that initialises the resource, and then the clock value can be used. Although that will still have the same problem: The Rhine is then not statically known.

@turion turion linked a pull request May 17, 2024 that will close this issue
1 task
@turion
Copy link
Owner Author

turion commented May 17, 2024

There is a big semantic change that I'm unsure about. On master the first tick is always a certain time after the initial time. Also, the duration sinceLast on the first tick is the time from the initial time the first tick. With this change here, we would have the awkward situation that sinceLast on the first tick is always 0! This is especially awkward for a fixed rate clock where we expect sinceLast to be largely constant. Instead, it is now 0 and then afterwards largely constant.

I'm not sure how to proceed then. I see several different stances:

  1. There should be some initialisation step that measures the initial time, and there shouldn't be any data processing on that step yet. This could be implemented in the original way, but there are a few other ways:
class Clock m cl where
  initialTime :: cl -> m (Time cl)
  initClock :: cl -> Automaton m () (Time cl, Tag cl)

or even

class Clock m cl where
  initialTime :: cl -> m (Time cl)
  initClock :: Automaton m cl (Time cl, Tag cl)

In latter case, the running clock is completely known statically, which is great.
2. On the first tick, sinceInit and sinceLast have no meaning and could have any values, e.g. 0 or 'Nothing'. This may be right, but is unhelpful in many cases and breaks with the established semantics. On the other hand, for CSV clocks (#226) and similar batch processing scenarios, there is no sensible start time other than a manually supplied one or the first timestamp.

@turion
Copy link
Owner Author

turion commented Jun 10, 2024

In latter case, the running clock is completely known statically, which is great.

But this also means that many clocks like RescaledClock or SelectClock cannot be implemented anymore.

@turion
Copy link
Owner Author

turion commented Jul 17, 2024

In latter case, the running clock is completely known statically, which is great.

But this also means that many clocks like RescaledClock or SelectClock cannot be implemented anymore.

But that's not such a problem if initClock is sufficiently inlined.

@ners
Copy link
Contributor

ners commented Aug 14, 2024

How about

initClockM :: m cl -> MSF m () (Time cl, Tag cl)

initClock :: cl -> MSF m () (Time cl, Tag cl)
initClock = initClockM . pure

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

Successfully merging a pull request may close this issue.

2 participants