On Sun, 05 Feb 2017 16:14:15 -0800, g...@google.com wrote: > > 1) A role is not a closure, so: > > } does role { method done { $done = True } } > > Will not behave as you want. > > > > It took me a minute to realize this was true, because if you move the > `my > $s2 = make-supply;` from the react section right up under the creation > of > $s1, it's clear that things go terribly wrong -- it apparently only > worked > for me because I was only creating one at a time and finishing with > one > before creating the next. > > That said, this raises two questions: > > A. How did this work in the first place? Was the role's reference to > $done > pointing to a single static slot? > Yes.
> B. Why isn't a role declaration a closure? I understand that the > attributes and methods need to be flattened into the composed class; > is > this because the contents of the role and the class are inserted into > one > combined block that can only have one outer lexical context? > It's because classes and roles are constructed at compile time; by runtime we're just referencing the one thing that was created at compile time. So, the role meta-object points to the single static instance of the role body block, and runs that every time. (The role body does run at runtime since this is a runtime mixin. However, even those get interned. Even if they didn't, we'd still be in the same situation, however, since there's a single static instance of the role body block.) > OK, the above makes sense to me, but why does the .act version work > properly then? When I first read that react {} was supplying actor- > like > semantics, I assumed that meant it works just like .act -- but it > doesn't. > Why not? What am I missing here? > The `act` version does have a problem too, in a sense. `act` returns a Tap object, and calling `.close` on that would be the correct way to close the supply being tapped. However, since that supply works synchronously, the call to `act` gets control and the `Tap` object doesn't become available. Really, `.act` just means `.serialize.sanitize.tap`. However, a `supply` block cannot emit multiple concurrent messages, so is already serial. It's also sanitary (follows the supply protocol), so the behavior in this case really is just `.tap`. So, the "actor-like" behavior of `.act` just means that there will never be concurrent calls to any of the blocks passed to that particular `.act` call. The `react` and `supply` constructs allow establishing of richer actors. The one-at-a-time applies to all of the whenever blocks. So: my $i = 0; $s1.act: { $i++ } $s2.act: { $i++ } Is a data race on $i, but: my $i = 0; react { whenever $s1 { $i++ } whenever $s2 { $i++ } } Is not. The problem you're running in to is that we also promise that: my $i = 0; react { whenever $s1 { $i++ } $i++; whenever $s2 { $i++ } } Will not be a data race - that is, the code inside of the main body of the react block holds the "lock" until it completes and all subscriptions and state are set up. But if $s1 or $s2 here do not give control back upon being tapped, then the react block's main body never completes and releases the lock either, and so it's impossible to process messages. > Well ... that kinda works. As I tried this (with a `sleep 2` added at > program end) and a few other variants -- using `last` instead of > `$s2.done` > as recommended in the irclog, using `loop` instead of `until $done`, > getting rid of the role application and instead putting `my $done = > False; > CLOSE $done = True;` inside the supply {} block, etc. -- I found that > every > variation I tried sometimes worked, and sometimes led to sadness. For > example, it might emit a largish number of times, then stop emitting > and > just hang (way past the length of the sleep). The version using > `last` and > `CLOSE` together would sometimes emit quite a few times before > exiting, > with the last few emits interspersed with `===SORRY!===` and `last > without > loop construct`. I assume the pile of emits before stopping is just a > matter of which thread was getting scheduled -- standard concurrency > issues. But the hang and the error make no sense. > Turns out the support for `last` inside of whenever blocks didn't get merged yet (it's in a PR, which I've looked at today, but seems to have some issues that need looking over beforehand). So that's the issue with `last`. Even if it was merged, there'd still be trouble. The real difficulty here is down to react/supply so far making the assumption that they are dealing with supplies that will deliver data asynchronously, and that will not block upon subscription. When the tap handle from subscription is not handed back before messages are emitted, there's no way for it to close the Supply. I'm still considering various ways we might be able to address this limitation, but it'll need some thinking time. > With all the things I tried, at this point I'm not even sure which > problems > were results of my ignorance and which were actual bugs of their own. > What > is the *correct and always working* version of this gist? Note that > in the > real input-processing code from which this toy example was extracted, > it's > critical to do cleanup after the supply is stopped, because otherwise > the > terminal will be stuck in raw input mode ... and that cleanup depends > on > restoring state saved just before the supply is set up. > I'd suggest something like this: sub make-supply() { my $s = Supplier::Preserving.new; my $done = False; start { until $done { $s.emit: ++$ } } $s.Supply.on-close({ say 'closing'; $done = True }) } say "\nUSING react"; my $s2 = make-supply; react { whenever $s2 -> $n { say "Received $n"; done if $n >= 5; } } > I thought the initial point of Supply was to address a few fundamental > limitations of Channel, one of which was to not force so much thread > switching just to send a stream of values through. It's a bit deeper than that. Supply and Channel are for different processing models. Channels are for when you want a queue that a producer can place things in to quickly, without blocking on whatever will process them. Something else receives and processes the messages. A channel is typically used to *introduce* parallel processing, and has the concurrency control in place to cope with that being 1:N, M:1, or N:M. Supplies were introduced to provide for the reactive paradigm, where values are being produced asynchronously and we wish to react to them in various ways and compose those various reactors. These values may come from a range of sources and arrive concurrently. Supplies are thus about taming/controlling concurrency. So, supplies don't replace channels; they solve a different set of problems that channels would not be suited to. It is true that supplies will process messages on the producing thread unless you explicitly say otherwise. Note that in the solution above, while we introduce a worker with the `start` block, all the `whenever` blocks inside of the `react` will be run on the thread of that `start` block. The thread doing the react will just wait for the react to be done (or, in 6.d.PREVIEW, if the react is in the thread pool, it will return to thread to the pool to get on with other work). > My understanding was > that (in the case of not explicitly starting a new thread for the > receiver), each value emitted would simply travel through a tree of > taps depth first before emitting the next value. In other words, `emit` > had coroutine status similar to `take`. It essentially does, but in the reactive space the yield just becomes a call (to process the reaction), and the resume is the return from that call. In common they have that the call is "abstract" (that is, with `take` the code is abstracted from a particular consumer, and with `emit` from a particular reactor). > And with .act() on the taps, that > seems to match my mental model. So why doesn't that work with react > {}? Because, as hopefully clarified above, react is trying to perform more concurrency control than act. Hope this helps, and I'll keep the issue under consideration.