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

What's the right order in Apply? #62

Open
tounano opened this issue Sep 4, 2023 · 13 comments
Open

What's the right order in Apply? #62

tounano opened this issue Sep 4, 2023 · 13 comments

Comments

@tounano
Copy link

tounano commented Sep 4, 2023

Hi,

What's the right order in apply.

What should be the result in the following case?

T.ap(T.Left(1), T.Left(2))

Following the same logic, what would be the right order on traverse?

ARR.traverse(T, x=>x, [T.Left(1), T.Left(2)])

Thanks.

@rpominov
Copy link
Member

rpominov commented Sep 4, 2023

Hey!

T.ap(T.Left(1), T.Left(2))

This example seem wrong because the first argument should be a value that contains a function. For example T.ap(T.Left((x) => x + 1), T.Left(2)). So, that's the order: function first.

ARR.traverse(T, x=>x, [T.Left(1), T.Left(2)])

This example also seems wrong. I would expect it to look something like this: ARR.traverse(T, x=>T.Left(x), [T.Left(1), T.Left(2)]). In this case, the result may be T.Left([1, 2]) assuming ARR.traverse is implemented like this:

traverse(A, f, a) {
  return a.map(f).reduce((r, i) => A.ap(A.map(a => i => a.concat([i]), r), i), A.of([]))
}

@rpominov
Copy link
Member

rpominov commented Sep 4, 2023

Ah, you're correct in the second example. It should be x => x, just like you wrote.

@semmel
Copy link

semmel commented Sep 4, 2023

This example seem wrong because the first argument should be a value that contains a function.

How so? Assuming @tounano's T is an Either, the left of it can be anything — mostly an Error though. See Either documented in crocks lib.

So since ap(mf, m) ≅ chain(f => map(f, m), mf), ap(E.Left(1), E.Left(2)) is Left 1.

const R = require('ramda');
const E = require('crocks/Either');
R.ap(E.Left(1), E.Left(2)).inspect(); // --> "Left 1"

Looking at the [E.Left(1), E.Left(2)] argument of the second example, I'd ask myself if sequence makes more sense than traverse.

R.sequence(E, [E.Left(1), E.Left(2)]).inspect() // --> "Left 1"

@rpominov
Copy link
Member

rpominov commented Sep 4, 2023

Right. I always forget which one is the error in Either.

In that case order completely depends on the specific implementation of Apply for Either. The spec has no opinion on it, as far as I can tell.

@tounano tounano closed this as completed Sep 4, 2023
@tounano
Copy link
Author

tounano commented Sep 4, 2023

Thanks everyone,

I'll do it like Crocks then.

I remember that long time ago I read about an issue with which error comes first in the case of Futures and Apply.

Maybe my memory betrays me.

In the case of a Future or any other Async type, it would mean which of the Futures would be forked first.

I'm developing a static land module for good old callbacks.

Thanks again, and would appreciate additional thoughts.

p.s. I close it by mistake.

@tounano tounano reopened this Sep 4, 2023
@semmel
Copy link

semmel commented Sep 4, 2023

@rpominov

Right. I always forget which one is the error in Either.

My way to remember: The right value must be right, right? So the left is left for the error.

@tounano

In the case of a Future or any other Async type, it would mean which of the Futures would be forked first.

I think Fluture has a "non-standard" pap (short for "parallel ap") for combining two asyncs. I guess it's because the standard demands (?) that ap(mf, m) ≅ chain(f => map(f, m), mf) and that would mean both Futures would have to run sequentially which is a huge inefficiency.

Because of that missing fantasyland spec of combining two asyncs in parallel, still today reactive stream libs like Bacon.js and @most/core have not adopted fantasyland.

@tounano
Copy link
Author

tounano commented Sep 4, 2023

Because of that missing fantasyland spec of combining two asyncs in parallel, still today reactive stream libs like Bacon.js and @most/core have not adopted fantasyland.

Hey,

Are you sure that's the reason? I don't think it's possible to do Apply on a stream. Since you'll need to replay the stream several times.

I mean from what I understand a stream is a one time thing, if you can replay it than it's an array, isn't it?

@tounano
Copy link
Author

tounano commented Sep 4, 2023

As for pap in future, I have something similar in my lib, and I'm going to export an interface like:

T.Serial
T.Parallel

where

T.Serial === T

The only difference would be is the ap.

But even if you develop a pap function, which error should precede in case both futures failed? Would that be a race, or should there be a logical order for the errors?

With fluture, I assume they use a race, since they have cancellation logic. I'm not going to have cancellation logic, so both futures going to run until the end.

I checked Crocks, they also use race logic, to display the first error fired.

Feels too random for me.

What I'm going to do is to display the mf error before the m error.

@semmel
Copy link

semmel commented Sep 4, 2023

@tounano

Are you sure that's the reason? I don't think it's possible to do Apply on a stream. Since you'll need to replay the stream several times.

I don't know what you mean with replay.

combine((a, b) => c, sa, sb) (aka liftA2) just combines the current event a of sa with the last of sb so it is effectively an event listener to both source streams and a publisher for sc.

Just the links to the discussions to implement Fantasyland: The bacon issue, the @most/core issue

@tounano
Copy link
Author

tounano commented Sep 4, 2023

I understand what you want now, and I've seen you commented that there is a conflict between monadic laws and applicative laws.

In any case, not sure you're familiar or not. For streams I'm using 'pull-stream' lib. It's not fantasy-land, neither static-land, but if you're using StaticLand, why does it matter?

pull-stream mostly use really small modules to do really small operations, so it's a fragmented staticland.

I developed something like you say for pull-streams, it's really handy as a streaming state of sort.

@semmel
Copy link

semmel commented Sep 4, 2023

@tounano

I'm not going to have cancellation logic, so both futures going to run until the end.
I checked Crocks, they also use race logic, to display the first error fired.
Feels too random for me.

Btw. Promise.any collects all errors (no race in the error channel) — so after all they do something right in the ECMAScrip committee? 😁

@tounano
Copy link
Author

tounano commented Sep 4, 2023

I guess there are some use cases for it, I just can't think of any :)

@semmel
Copy link

semmel commented Sep 4, 2023

@tounano

I checked Crocks, they also use race logic, to display the first error fired.
Feels too random for me.
What I'm going to do is to display the mf error before the m error.

I would not do that.

In that case liftA2(R.add)(fut1, fut2) would behave different from liftA2(R.add)(fut2, fut1)?
I mean, if fut1 always fails before fut2 fails, it would only surface in one of the two combinations?

The idea that your liftA2(comb) is not symmetric in the arguments is really disturbing.

Racing, or collecting all is ok, imo.

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

No branches or pull requests

3 participants