problem-solving icon indicating copy to clipboard operation
problem-solving copied to clipboard

Make react return values

Open FCO opened this issue 4 years ago • 22 comments

It would make react even more useful if that could return data from inside its block.

FCO avatar Dec 02 '20 23:12 FCO

I suggest doing that by making done accept an optional value and make react return it. So something like this

say react whenever Promise.in: 1 { done 42 }

would print 42

FCO avatar Dec 02 '20 23:12 FCO

would that make sense?

FCO avatar Jun 09 '21 17:06 FCO

I guess the best way to find out is to write a PR for it:

This would involve adding a payload to the control message throwing in sub done and replacing the Nil in sub REACT with the value from the payload?

lizmat avatar Jun 09 '21 18:06 lizmat

I really like this idea – it would increase the parallelism between react and gather. How would done work outside of a react block? Still accept a value and return it somewhere? Throw an error? Discard the value?

codesections avatar Jun 09 '21 20:06 codesections

Throw an error:

 $ raku -e  'done'
 done without supply or react
  in block <unit> at -e line 1

lizmat avatar Jun 09 '21 20:06 lizmat

@jnthn opinion?

lizmat avatar Jun 09 '21 20:06 lizmat

done without supply or react

Sorry, I wasn't clear: I meant how to handle done in a supply context, not in an arbitrary context.

codesections avatar Jun 09 '21 21:06 codesections

@jnthn opinion?

There's really two requests in one here, and I feel very differently about them.

The first is that a react block might produce a value. This breaks the principle that react is for side-effects and supply is for the production of values. The very reason I introduced react to the language was for situations where we want to implement a sink of events that will react to them by doing side-effects. Thus I find this proposal simply too corrosive to the design to accept.

The proposal for done with a value is OK by me. In fact, the S04 design document proposes that the last control exception thrower could take an optional value argument, which would become the result of the final iteration (in the case that a loop is being evaluated for its values). That's the interactive dual of what is being proposed here. We could implement this behavior of last at the same time we do it for done, then it's nice and consistent. Anyway, a tentative +1 on this part.

Returning to the problem as a whole. The semantics of await on a Supply are to tap it, keep track of the latest value it has emitted, and produce that value when it is done (or throw an exception if the Supply should quit). Combined with a done that takes a value, we can write:

say await supply whenever Promise.in: 1 { done 42 }

Which is only one word longer than what was originally requested.

jnthn avatar Jun 09 '21 22:06 jnthn

Ah, and a further point: it's not uncommon to write an emit followed by a done in a supply block today also, so this would find far more general use.

jnthn avatar Jun 09 '21 22:06 jnthn

Problem with last taking a value to return, is that last can already take a value. Well, if that happens to be a Label:D. I guess we probably do not want to last a Label ?

lizmat avatar Jun 17 '21 14:06 lizmat

@lizmat Yeah, S04 speculated using multiple dispatch to resolve that. Agree that one would pretty much never be using a Label as data.

jnthn avatar Jun 17 '21 15:06 jnthn

Ah, and a further point: it's not uncommon to write an emit followed by a done in a supply block today also, so this would find far more general use.

Hmmm, would this be a way to address the "chatty WebSocket" problem with Cro, where it follows the last data frame in a message with an empty fin frame, because emit and done have so far been separate operations?

I feel like there's a little more work to do to take advantage of this, but my brain is not fully engaged at the moment, so I'm not seeing exactly what that work would be.

japhb avatar Jun 17 '21 21:06 japhb

@japhb possibly.

Meanwhile I'm trying to grok what the semantics of dd ^10 .grep: { $_ < 5 || last 42 } would be. (0,1,2,3,4,5).Seq ? Or (0,1,2,3,4,42).Seq ?

lizmat avatar Jun 17 '21 21:06 lizmat

(0,1,2,3,4,5).Seq

I think this must be the right one. It doesn't look right if grep would produce anything but what it is supplied with.

vrurg avatar Jun 17 '21 21:06 vrurg

Hmmm, would this be a way to address the "chatty WebSocket" problem with Cro, where it follows the last data frame in a message with an empty fin frame, because emit and done have so far been separate operations?

No, this is purely a convenience on the sender side. done $x should be precisely equivalent to emit $x; done.

jnthn avatar Jun 17 '21 21:06 jnthn

It doesn't look right if grep would produce anything but what it is supplied with.

That's my thought too.

Looking at &take, it seems that the truthiness of 42 matters as well, so dd ^10 .grep: { $_ < 5 || last 0 } would be (0, 1, 2, 3, 4).Seq. Does that seem right as well?

codesections avatar Jun 17 '21 21:06 codesections

I'd forgotten you even could use next, last, etc. in grep, and wonder if this was an intended feature, or an accident of grep being implemented in terms of map. If grep really is defined as relying on map for its loopiness then the 42 ending up in the result list is the rather unfortunate logical consequence of that. We will need to pick a semantic for now, but I'd not be entirely sad to see redo/next/last on a grep ending up deprecated...does anyone have an example where they are useful rather than baffling?

jnthn avatar Jun 17 '21 21:06 jnthn

does anyone have an example where [using next, last, etc. in grep is] useful rather than baffling?

I think the main use-case for last in grep is to short-circuit when eagerly grepping over a long list for performance reasons.

In other languages, I've seen cases where code would have been more readable with a .filter but was written as a for loop precisely to allow for short-circuiting. Raku's support for laziness helps, but there are still cases where code will need to be eager.

[Edit: I can't think of any possible reason for using redo or next inside a grep, though, unless the code is doing something a bit crazy in terms of side effects. Maybe if grep is being used with gather/take?

Also, the semantics of grep are pretty much identical to a map that sometimes returns Empty, which I guess would give people a way to short circuit when filtering a list even if they can't do so inside grep]

codesections avatar Jun 17 '21 22:06 codesections

I think the main use-case for last in grep is to short-circuit when eagerly grepping over a long list for performance reasons.

Ah yes, I'd overlooked, and agree it's of value. I guess when doing that, you'd want to choose whether or not to include the current value, and boolifying the argument to last would be the logical way to do that. So yes, I think I concur with your expectations on that.

jnthn avatar Jun 17 '21 22:06 jnthn

FWIW, I also dislike next, redo and last in .grep and .map.

next and redo are simply meh.

last could perhaps be replaced by returning IterationEnd as value?

lizmat avatar Jun 18 '21 08:06 lizmat

FWIW, I also dislike next, redo and last in .grep and .map.

Well, in map they are there because:

  1. for is defined in terms of map (with appropriate sink/eager and serial enforcement); and for in any non-trivial or non-sink case is even compiled into a call to map.
  2. From the perspective of the language user, it allows one to easily refactor between for and map

could perhaps be replaced by returning IterationEnd as value?

I only really would want to see IterationEnd show up in code that is implementing an Iterator.

jnthn avatar Jun 18 '21 09:06 jnthn