http://qs321.pair.com?node_id=736137


in reply to regarding 1.02 (was Re: IO::Lambda: call for participation)
in thread IO::Lambda: call for participation

Does it help at all if I re-phrase and re-format it like this (pretend that the predicates are prefixed with 'on_' even though they aren't):

lambda sub { context $socket; on_writable sub { print $socket "GET $url HTTP/1.0\r\n\r\n"; my $buf = ''; on_readable sub { my $n = sysread( $socket, $buf, 1024, length($buf)); return "read error:$!" unless defined $n; return $buf unless $n; again; } } }

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.

Replies are listed 'Best First'.
Re^2: regarding 1.02 (was Re: IO::Lambda: call for participation)
by tilly (Archbishop) on Jan 14, 2009 at 06:11 UTC
    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.
    lambda { context $socket; writable { print $socket "GET $url HTTP/1.0\r\n\r\n"; my $buf = ''; readable { my $n = sysread( $socket, $buf, 1024, length($buf)); return "read error:$!" unless defined $n; return $buf unless $n; again; }; }; };
    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.
      The minor thing I decided not to correct, but just changed that decision: it's not quite

      This sets up one of many parallel closures that process in parallel.

      but rather

      This sets up a new lambda object with attached one of many closures that process sequentially.

      Per one lambda, only one writable will ever be executed; after it in turn registers readable, socket won't be listened for on_write events. Same is valid for readable.

        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):

        my ($req, $socket, $buf); sub talk { $req = shift; $socket = IO::Socket::INET-> new( PeerAddr => 'www.perl.com', PeerPo +rt => 80); return lambda \&init; } sub init { when_writeable( $socket, \&write_handler ); } sub write_handler { send_request( $req => $socket ); when_readable( $socket, \&read_handler ); } sub read_handler { read_stuff( $socket => $buf ) or die; if ( done() ) { return $buf; } else { when_readable( $socket, \&read_handler ); } } my $q = talk( HTTP::Request-> new( GET => 'http://www.perl.com') ); print $q->wait;

        -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.