cujojs / most

Ultra-high performance reactive programming
MIT License
3.5k stars 231 forks source link

Mention zero and foldable algebraic types #479

Closed TrySound closed 5 years ago

TrySound commented 6 years ago

Plus::zero is implemented via empty, never methods Not sure about scan, but reduce looks like Foldable::reduce

We can add some examples with laws around these types.

briancavalier commented 6 years ago

Hi @TrySound. As it stands, I don't think we can mention either Plus or Foldable. I'll try to explain why for each.

Foldable

If you look carefully at the type definition of Foldable:

reduce :: Foldable f => f a ~> ((b, a) -> b, b) -> b

you'll notice that given a function (b, a) -> b, reduce must be able to return a b synchronously. That's impossible in the general case with an asynchronous data structure like a mostjs event stream. The best it can do in the general case is to return a future b, iow a promise for b, which violates Foldable.

Plus

Plus requires the type first to implement Alt:

A value that implements the Plus specification must also implement the Alt specification.

So, since mostjs doesn't currently implement Alt, it wouldn't be valid, for example, to alias empty() or never() to zero() and claim support for Plus.

That raises the question: could we implement Alt? The core team has discussed this, and if memory serves, we believe that there are at least two possible Alt implementations, each of which implies a different Plus implementation:

  1. Alt is a race. This implies never() as the correct Plus zero().
  2. Alt is a continuation, i.e. "stream 1 until it ends, and then stream 2", which is sort of like continueWith or concat. This implies empty() as the correct Plus zero(). There is a precedent for this in that [netwire]'s Alternative instance is similar.

It's not clear which of these is the more useful and/or correct Alt/Plus. My most recent thinking has been that, since concat was removed in @most/core, it'd be reasonable to introduce Alt as option 2 above, with empty() as Plus zero(). But, honestly, it's still not entirely clear what is best.

Any thoughts on that? Do you see other options for a valid and useful Alt?

TrySound commented 6 years ago

Was concat remove because of event time breaking? I like the idea of empty/continueWith pair

briancavalier commented 6 years ago

Was concat remove because of event time breaking?

Yeah, and it just has a misleading mental model. You can read more here: https://github.com/mostjs/core/pull/80

TrySound commented 6 years ago

So do we leave plus and alt here?

briancavalier commented 6 years ago

Yeah, I'm up for adding them. I'd like to hear what @TylorS, @davidchase, and @Frikki think about it.

If we add them, this PR will need a few more todos:

If we all agree this is the right direction, then I'll be happy to help.

briancavalier commented 6 years ago

After some offline discussion, I think we're good to go on this. @TrySound, are you up for handling the todos above?

TrySound commented 6 years ago

Sure.

briancavalier commented 6 years ago

Awesome, thank you!

TrySound commented 6 years ago

@briancavalier What about monoid/semigroup types? They look similar, but removing concat can break semigroup.

briancavalier commented 6 years ago

Semigroup and Monoid are similar to Alt and Plus. Semigroup is a superclass of Monoid (see here and diagram here). IOW, a type must first implement Semigroup before it can be a Monoid. So, if concat is removed, then it's no longer a Semigroup, and thus, by definition, no longer a Monoid.

The two hierarchies are similar, but conceptually, it can be helpful sometimes to think of Semigroup/Monoid as representing merging or combining, and Alt and Plus as representing choice or alternatives.

TrySound commented 6 years ago

You use merge word here. So can be semigroup considered, not only like appending in the end, but combining values in some way?

briancavalier commented 6 years ago

@TrySound English is imprecise, so in reality, the only things that matter are the type signature and mathematical laws by which concat must abide:

concat :: forall s. (Semigroup s) => s -> s -> s

You can substitute Stream a for s and get:

concat :: forall a. Stream a -> Stream a -> Stream a

That is, given 2 streams of as, produce another stream of as. And there is an associativity law:

concat(s1, concat(s2, s3)) ~= concat(concat(s1, s2), s3)

Appending and time-based interleaving are both valid implementations. It is possible to have a Semigroup that combines values, but only in the case where you know the values (i.e. the as are also a Semigroup). For example, it's typical for a Maybe structure, such as purescript's Maybe, where Maybe a is a Semigroup only if a is a Semigroup.

An analogy for an event stream would be a merge operation that requires a to be a Semigroup and concat's the as when events are simultaneous in the two streams being merged.

Currently, most.js's concat appends one stream to another. This was a bad decision for a data structure in which times are just as important as values, because appending causes time shifting. It's likely in most.js 2.0, we'll switch the fantasy-land Semigroup to use merge() instead, which preserves times.

TrySound commented 6 years ago

Added separate test file for Alt and Plus (do you want to split them?). Covered continueWith, alt, empty, zero with type tests. Do we need any parameter in continueWith? Is it kind of legacy? Is it ok to use es features in tests?

TrySound commented 6 years ago

Nothing is deprecated in this PR. Just aliases for algebraic types like just/of.

Frikki commented 6 years ago

My bad, @TrySound. The deprecate comments were already there. However, I’ll then alert @briancavalier on this practice.