When the lambda closure is executed, the 'on_writable' sets a callback to be
executed when the $socket is writable. When the closure finishes,
IO::Lambda sees that the socket is writable and executes the callback.
That callback executes and it sets another callback for when the socket is
readable. When the writable callback finishes, IO::Lambda sees that the
socket is readable and executes the readable callback. That callback
returns a value when all input is read, or else re-queues itself for the
next time the socket is readable using again.

When all that is done, the value returned from running (er, 'wait'-ing
for the lambda) is the value returned from the last callback to run -- in
this case, the "return $buf".

The 'readable' part has to be set after the 'writable' part runs,
otherwise, IO::Lambda could call them in any order, trying to read from the
socket before the request is sent.

At least, that's how I think it works.

I agree that the nesting syntax is confusing and the way values are
returned is likewise confusing.

-xdg

Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

I thought it should be read differently, then I read it more carefully and I agree with your reading. But since seeing the same thing said multiple ways often helps, let me present it my way. First merlyn's example, formatted differently.

Here it is again with comments stating what each piece does according to my understanding.

# This sets up one of many parallel closures that process
# in parallel. It will be called at the start.
lambda {
# This sets the context of what connection this happens
# on. This association is remembered within the engine.
context $socket;
# writeable sets up a possible event to monitor, when
# $socket is writeable, execute the closure.
writable {
# The engine discovered we can write, so do so.
print $socket "GET $url HTTP/1.0\r\n\r\n";
# This variable needs to stay shared across
# multiple invocations of our readable closure, so
# it needs to be outside that closure.
my $buf = '';
# readable registers another event to monitor -
# that $socket is readable. Note that we do not
# need to set the context again because when we get
# here, the engine knows what context this command
# took place in, and assumes the same context.
readable {
# This closure is executed when we can read.
my $n = sysread( $socket, $buf, 1024, length($buf));
# If we return without registering a follow-up
# handler, this return will be processed as the
# end of this sequence of events for whoever is
# waiting on us.
return "read error:$!" unless defined $n;
return $buf unless $n;
# We're not done so we need to do this again.
# Note that the engine knows that it just
# called this closure because $socket was
# readable, so it can infer that it is supposed
# to set up a callback that will call this
# closure when $socket is next readable.
again;
};
};
};

And here we see the reason for the nesting. You nest whenever one action is contingent on another having already happened. Given that lambda just registers a callback, and you always want to do something, somewhere, you always nest at least once. But you can nest more times.

One thing that is still not entirely clear here is how the $socket parameter gets passed aroud - or rather: why it needs to be passed in two ways - as a shared variable in the closure and via context? Actually - maybe I know the answer - this is because the "conditions" read from the context.

It needs to be passed in the context because the dispatch engine needs to know about $socket so it knows when to dispatch to the closure. $socket also needs to be available in the closure, so the easiest way to do that is to make it part of the closure. However if you want the context is also available in @_. It just takes more work to get it from there.

Whoa, that is a surprise, thank you! May I ask if I can copy your code comments into the documentation? If you should like to do the same to other comments or code, you are very welcome, I'd be happy with such help.

I would be glad to have you copy my comments into the documentation. Unfortunately I have no projects that would benefit from asynchronous IO, but if I did I'd give your project a try because it looks interesting and I'm impressed with how much you have.

So these are really "on_next_writable" and "on_next_readable" rather than general event handlers. (Unless they are reset with again().) That's definitely different from POE, in which event handlers persist once set.

So, conceptually, it's sort of like this (in a sort of pseudo-code and with some lexical scope differences so that the function references are explicit which I think makes the order of execution clearer):

Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.