Giter Club home page Giter Club logo

Comments (12)

pyrtsa avatar pyrtsa commented on August 10, 2024

My recommendation is to remove coalesce and (the combiner-less overload of) merge altogether¹, and to make StreamSink assert when more than one event is being sent with sink.send(value).

@romansl I think you've got a point we can solve there, but I'm not entirely following – what exactly makes Sodium.tx dangerous here?

¹) For convenience, the library could provide the left and right biased combiner functions separately.

from sodium.

romansl avatar romansl commented on August 10, 2024

The marge without arguments can combine events to Pair<A, A> or List<A>(2).

Also marge can have signature Stream<A>.marge(b: Stream<B>, combiner: (A, B) -> C).

Transaction is dangerous because can accidentally loose important events.

from sodium.

pyrtsa avatar pyrtsa commented on August 10, 2024

I don't think Pair<A, A> is a good default. You'd only merge two things like that. But then when you have 3 streams you want to merge, you'd end up with something like Pair<A, Pair<A, A>>. …Let alone N streams to merge!

The merging of streams has always been closely related to monoids or actually, since there never ever is a stream firing with nothing – that'd be absurd! – semigroups.

With that thought in mind, the way to merge things into a List<A> would be to map each of the inputs into single-element List<A>'s, and then merge those with list concatenation as the combiner.

In Haskell, we could even redefine the old merge conditionally as:

merge :: Semigroup a => Stream a -> Stream a -> Stream a
merge x y = mergeWith (<>) x y  -- or, for short: merge = mergeWith (<>)

which is exactly what I described above.

Further, Stream a could be made conditionally a Monoid if there's a default way to append a's together:

instance (Semigroup a) => Monoid (Stream a) where
    mempty = never
    mappend = merge

from sodium.

pyrtsa avatar pyrtsa commented on August 10, 2024

Transaction is dangerous because can accidentally loose important events.

Yeah, I'd try hard to make it impossible to send to the same stream more than once per transaction. If it can't be done with the interface, asserting a precondition would be my second best suggestion:

val sink = streamSink<Command>()

Sodium.tx {
    sink.send(BeginCommand())
    sink.send(SomeCommand()) // throws Exception: "StreamSink already sent"
    // ...
}

from sodium.

romansl avatar romansl commented on August 10, 2024

Throwing exceptions makes it even worse. That if this happens inside logic construction?

And here we got that firing of the event in one part of code may affect another.

The send must be as safe as possible.

from sodium.

romansl avatar romansl commented on August 10, 2024

May be send must produce new transaction like defer?

from sodium.

pyrtsa avatar pyrtsa commented on August 10, 2024

I think we come from different schools of thought here, and there's no Proven Right Way™ to write real-world FRP applications yet, so I value your ideas. Would like to understand them better.

I was talking about enforcing a precondition against sending multiple firings into a StreamSink. Whether it's an Exception or just Log.fatal("...") is irrelevant.

And here we got that firing of the event in one part of code may affect another.

In my view, transactions should be very local beasts that do as little as possible, stuff like update the mouse state into what it currently is or such. One transaction shouldn't contain many "parts of code" (but I'd be interested in seeing what the counterexample would be).

That if this happens inside logic construction?

What do you mean by inside logic construction?

from sodium.

the-real-blackh avatar the-real-blackh commented on August 10, 2024

I've checked in ed0c403 which changes it so an exception is now thrown if send() is called more than once per transaction. Now we have this:

  • coalesce() doesn't exist in the API.
  • send() throws an exception by default if called more than once per transaction. This will generally not be a problem during a "one big transaction" program initialization because the code will tend to be deterministic so the bug will tend to be caught during testing. It will never be a problem where a transaction is started implicitly.
  • Passing a combining function to StreamSink() allows you to change this policy.
  • s1.merge(s2) without f will throw events away. This may seem dangerous but it's a tradeoff against convenience, because the case where a merge is done when the user knows the event can never be simultaneous is extremely common. Use of the Unit type here (where throwing one away doesn't matter) is also common. Convenience of the user interface is important to me because I see Sodium as competing for mainstream approval against the broken FRP implementations (Rx and friends).

Other options I didn't choose:

  1. s1.merge(s2) throws an exception. I think in a practical situation this is more dangerous than dropping the s1 event.
  2. static <A extends SemiGroup> Stream.merge(Stream<A> s1, Stream<A> s2) would be extremely cumbersome in Java but the equivalent Haskell with typeclasses would work and I will most likely use that. Haskell programmers are used to much stricter APIs than Java programmers are.

So let me know if you think we can do better than this.

from sodium.

ritschwumm avatar ritschwumm commented on August 10, 2024

how about naming s1.merge(s2) "orElse" instead? it more clearly describes what the method actually does. and of course i'm used to scala's Option#orElse ...

from sodium.

the-real-blackh avatar the-real-blackh commented on August 10, 2024

That's certainly possible, but in the non-simultaneous case it's still a merge operation. Hmm.... I'm starting to like the idea.

from sodium.

romansl avatar romansl commented on August 10, 2024

It is good idea I think.

from sodium.

the-real-blackh avatar the-real-blackh commented on August 10, 2024

merge() renamed to orElse() as suggested.

from sodium.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.