IO::Lambda(3)
π
IO::Lambda(3) β User Contributed Perl Documentation π
okay so bestie, this is the IO::Lambda(3) page from the User Contributed Perl Documentation, and itβs giving non-blocking I/O as lambda calculus. Part 1 of 6, so buckle up. β¨
πΈ NAME πΈ
IO::Lambda β non-blocking I/O as lambda calculus. π
π¦ SYNOPSIS π¦
The code below demonstrates execution of parallel HTTP requests. No cap, this is the whole demo, verbatim β Iβm not touching a single character of the code, bestie. π«Ά
use strict;
use IO::Lambda qw(:lambda :func);
use IO::Socket::INET;
# this function creates a new lambda object
# associated with one socket, and fetches a single URL
sub http
{
my $host = shift;
# Simple HTTP functions by first sending request to the remote, and
# then waiting for the response. This sets up a new lambda object with
# attached one of many closures that process sequentially
return lambda {
# create a socket, and issue a tcp connect
my $socket = IO::Socket::INET-> new(
PeerAddr => $host,
PeerPort => 80
);
# Wait until socket become writable. Parameters to writable()
# are passed using context(). 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 send the request
print $socket "GET /index.html 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. Here, it collects
# whatever the remote returns
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.
# Also note that socket won't be awaited for writable events
# anymore, and this code won't be executed for this $socket.
readable {
# This closure is executed when we can read.
# Read from the socket. sysread() returns number of
# bytes read. Zero means EOF, and undef means error, so
# we stop on these conditions.
# 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 $buf unless
sysread( $socket, $buf, 1024, length($buf));
# 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;
}}}
}
# Fire up a single lambda and wait until it completes.
print http('www.perl.com')-> wait;
# Fire up a lambda that waits for two http requests in parallel.
# tails() can wait for more than one lambda
my @hosts = ('www.perl.com', 'www.google.com');
lambda {
context map { http($_) } @hosts;
# tails() asynchronously waits until all lambdas in the context
# are finished.
tails { print @_ }
}-> wait;
# crawl for all urls in parallel, but keep 10 parallel connections max
print par(10)-> wait(map { http($_) } @hosts);
# crawl for all urls sequentially
print mapcar( curry { http(shift) })-> wait(@hosts);
Note:"io"and"lambda"are synonyms β I personally prefer"lambda", but some find the word slightly inappropriate, hence"io". See however Higher-order functions to see why it is more"lambda"than"io". π
π DESCRIPTION π
okay so bestie, listen β this module is another attempt to fight the horrors of non-blocking I/O. Like, genuinely. It's trying to bring back the simplicity of the declarative programming style, the one you only get when you're out here using threads, coroutines, or co-processes. π
Because here's the tea: coding non-blocking I/O for single-process, single-thread programs means you have to build state machines β often fairly complex ones β and ngl that does not help your code clarity, and it's literally the reason asynchronous I/O programming has a reputation for being "messy".
It's kind of like the concept of monads in functional languages, which enforce a certain order of execution over generally orderless functions. Same energy: IO::Lambda lets you write I/O callbacks in a style that resembles the good old sequential, declarative programming. π«Ά
Now, about how this manual is laid out β it begins with code examples, then proceeds to explaining basic assumptions, then finally gets to the complex concepts, where the real fun begins. π₯
- You can skip directly to the fun stuff ("Stream IO", "Higher-order functions"), where the functional style mixes with I/O.
- If, on the contrary, you are intimidated by the module's ambitions, skip to "Simple use" for a more gentle introduction.
- And those of you who are interested in how this module is different from the other I/O frameworks? please continue reading. π
πΈ Simple use πΈ
This section is for those who don't need all of the module's powerful machinery. Simple callback-driven programming examples show how to use the module for unsophisticated tasks, using concepts similar to the other I/O frameworks. It is possible to use the module on this level only β however, one must be aware that by doing so, the real power of the higher-order abstraction is not used. π
IO::Lambda, like all I/O multiplexing libraries, provides functions for registering callbacks, that in turn are called when a timeout occurs, or when a file handle is ready for reading and/or writing. See below code examples that demonstrate how to program on this level of abstraction. β¨
Combination of two timeouts and an IO_READ event
use IO::Lambda qw(:constants);
my $obj = IO::Lambda-> new;
# Either 3 or time + 3 will do. See "Time" section for more info
$obj-> watch_timer( 3, sub { print "I've slept 3 seconds!\n" });
# I/O flags is a combination of IO_READ, IO_WRITE, and IO_EXCEPTION.
# Timeout is either 5 or time + 5, too.
$obj-> watch_io( IO_READ, \*STDIN, 5, sub {
my ( $self, $ok) = @_;
print $ok ?
"stdin is readable!\n" :
"stdin is not readable within 5 seconds\n";
});
# main event loop is stopped when there are no lambdas and no
# pending events
IO::Lambda::run;
Waiting for another lambda to complete
use IO::Lambda;
my $a = IO::Lambda-> new;
$a-> watch_timer( 3, sub { print "I've slept 3 seconds!\n" });
my $b = IO::Lambda-> new;
# A lambda can wait for more than one event or lambda.
# A lambda can be awaited by more than one lambda.
$b-> watch_lambda( $a, sub { print "lambda #1 is finished!\n"});
IO::Lambda::run;
π¦ Example: reading lines from a filehandle π¦
Given $filehandle is non-blocking, the following code creates a lambda object (later, simply a lambda) that reads from the handle until EOF or an error occured. Here, "getline" (see "Stream IO" below) constructs a lambda that reads a single line from a filehandle. π
use IO::Lambda qw(:all);
sub my_reader
{
my $filehandle = shift;
lambda {
context getline, $filehandle, \(my $buf = '');
tail {
my ( $string, $error) = @_;
if ( $error) {
warn "error: $error\n";
} else {
print $string;
return again;
}
}}
}
Assume we have two socket connections, and sockets are non-blocking β read from both of them in parallel. The following code creates a lambda that reads from two readers:
sub my_reader_all
{
my @filehandles = @_;
lambda {
context map { my_reader($_) } @filehandles;
tails { print "all is finished\n" };
}
}
my_reader_all( $socket1, $socket2)-> wait;
π Non-blocking HTTP client π
Given a socket, create a lambda that implements the HTTP protocol:
use IO::Lambda qw(:all);
use IO::Socket;
use HTTP::Request;
sub talk
{
my $req = shift;
my $socket = IO::Socket::INET-> new( PeerAddr => 'www.perl.com', PeerPort => 80);
lambda {
context $socket;
writable {
# connected
print $socket "GET ", $req-> uri, "\r\n\r\n";
my $buf = '';
readable {
sysread $socket, $buf, 1024, length($buf) or return $buf;
again; # wait for reading and re-do the block
}
}
}
}
Connect and talk to the remote:
$request = HTTP::Request-> new( GET => 'http://www.perl.com');
my $q = talk( $request );
print $q-> wait; # will print content of $buf
Connect two parallel connections: by explicitly waiting for each π
$q = lambda {
context talk($request);
tail { print shift };
context talk($request2);
tail { print shift };
};
$q-> wait;
Connect two parallel connections: by waiting for all β
$q = lambda {
context talk($request1), talk($request2);
tails { print for @_ };
};
$q-> wait;
Teach our simple http request to redirect by wrapping talk(). talk_redirect() will have exactly the same properties as talk() does β no cap. π
sub talk_redirect
{
my $req = shift;
lambda {
context talk( $req);
tail {
my $res = HTTP::Response-> parse( shift );
return $res unless $res-> code == 302;
$req-> uri( $res-> uri);
context talk( $req);
again;
}
}
}
π Full example code π
use strict;
use IO::Lambda qw(:lambda);
use IO::Socket::INET;
sub get
{
my ( $socket, $url) = @_;
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;
}}}
}
sub get_parallel
{
my @hosts = @_;
lambda {
context map { get(
IO::Socket::INET-> new(
PeerAddr => $_,
PeerPort => 80
), '/index.html') } @hosts;
tails {
join("\n\n\n", @_ )
}
}
}
print get_parallel('www.perl.com', 'www.google.com')-> wait;
See tests and additional examples in directory "eg/" for more information. π«Ά
π¦ API π¦
β¨ Events and states β¨
A lambda is an "IO::Lambda" object, that waits for I/O and timeout events, and for events generated when other lambdas are completed. On each such event a callback is executed. The result of the execution is saved, and passed on to the next callback, when the next event arrives. π
Life cycle of a lambda goes through three modes: passive, waiting, and stopped. A lambda that is just created, or was later reset with "reset" call, is in the passive state. When the lambda gets started, the only executed code will be the callback associated with the lambda:
$q = lambda { print "hello world!\n" };
# not printed anything yet
$q-> wait; # <- here it will
Lambdas are usually not started explicitly. Usually, the function that can wait for a lambda, starts it too. "wait", the synchronous waiter, and "tail"/"tails", the asynchronous ones, start passive lambdas when called. A lambda is finished when there are no more events to listen to. The lambda in the example above will finish right after "print" statement. β
Lambda can listen to events by calling conditions, that internally subscribe the lambda object to the corresponding file handles, timers, and other lambdas. Most of the expressive power of "IO::Lambda" lies in the conditions, such as "readable", "writable", "timeout". Conditions are different from normal perl subroutines in the way how they receive their parameters. The only parameter they receive in the normal way is the associated callback, while all other parameters are passed to it through the alternate stack, by the explicit "context" call. π
In the example below, lambda watches for file handle readability:
$q = lambda {
context \*SOCKET;
readable { print "I'm readable!\n"; }
# here is nothing printed yet
};
# and here is nothing printed yet
Such lambda, when started, will switch to the waiting state, which means that it will be waiting for the socket. The lambda will finish only after the callback associated with "readable" condition is called. Of course, new event listeners can be created inside all callbacks, on each state. This fact constitutes another large benefit of "IO::Lambda", as it allows to program FSMs dynamically. π₯
The new event listeners can be created either by explicitly calling condition, or by restarting the last condition with the "again" call. For example, code
readable {
print 1;
again if int rand(2)
}
prints indeterminable number of ones. π
π Contexts π
All callbacks associated with a lambda object (further on, merely lambda) execute in one, private context, also associated to the lambda. The context here means that all conditions register callbacks on an implicitly given lambda object, and keep the passed parameters on the context stack. The fact that the context is preserved between states helps building terser code with series of IO calls:
context \*SOCKET;
writable {
readable {
}}
is actually the shorter form for
context \*SOCKET;
writable {
context \*SOCKET; # <-- context here is retained from one frame up
readable {
}}
And as the context is bound to the current closure, the current lambda object is too, in "this" property. The code above is actually
my $self = this;
context \*SOCKET;
writable {
this $self; # <-- object reference is retained here
context \*SOCKET;
readable {
}}
so this is the whole "keep a grip on the object" vibe β this is your girl when more than one lambda needs to be reached. In that case,
this $object;
context @context;
is literally the same thing as
this $object, @context;
which β heads up π«Ά β means that explicitly setting this will always clear the context. no exceptions, no leftovers, periodt.
β¨ Data and execution flow β¨
okay so first things first: a lambda gets called from the outside with some arguments. those args can be stashed away with the call method β but ngl, wait and tail also fire off call internally, so they'll replace whatever data was sitting there from a previous call. inside the lambda, all that good stuff shows up as @_. π
and then: whatever a condition callback returns (yes, including the lambda condition itself, she's not special) gets passed along as @_ to the next callback β or to the outside world if the lambda is done-done. you can grab the result of a finished lambda with peek, which returns the whole array of data in array context, or just the first item otherwise. and wait? yeah, it hands you back the exact same data peek would.
here's a sneaky one π β when more than one lambda is watching another lambda, the watched one passes its last callback results to all the watchers. but when a lambda spins up more than one state that derives from the current state (a little forking situation, iykyk), the latest stored results get overwritten by whichever callback runs first. so a construction like
readable { 1 + shift };
writable { 2 + shift };
...
wait(0)
will eventually return 3, but whether it's 1+2 or 2+1? undefined. fr fr, don't rely on it.
wait is not the only function out here synchronising input and output data, bestie. you've also got:
wait_for_allβ waits for all lambdas, including the caller, to finish. returns the collected results of all the objects in a single list.wait_for_anyβ waits for at least one lambda from the list you pass (again, caller included) to finish. returns a list of the finished objects ASAP. β‘
πΈ Time πΈ
okay this is the iconic part. timers and I/O timeouts can be given not only as timeout values (which is what every event library does, boring), but also as deadlines in (fractional) seconds since epoch. weird at first glance? sure. but it actually slays when you need to track a total execution time. like, this code reads as many bytes as possible from a socket within 5 seconds:
lambda {
my $buf = '';
context $socket, time + 5;
readable {
if ( shift ) {
return again if sysread $socket, $buf, 1024, length($buf);
} else {
print "oops! a timeout\n";
}
$buf;
}
};
now imagine rewriting that same code with readable semantics that take a timeout instead of a deadline β the result is, uh, not it π:
lambda {
my $buf = '';
my $time_left = 5;
my $now = time;
context $socket, $time_left;
readable {
if ( shift ) {
if (sysread $socket, $buf, 1024, length($buf)) {
$time_left -= (time - $now);
$now = time;
context $socket, $time_left;
return again;
}
} else {
print "oops! a timeout\n";
}
$buf;
}
};
but plot twist π β the exact opposite is true for timeout. both of these lines sleep 5 seconds:
lambda { context 5; timeout {} }
lambda { context time + 5; timeout {} }
internally, timers use Time::HiRes::time, which gives you the fractional number of seconds. and no, the caller doesn't have to care about that β if high-res timers aren't available, timeouts just get less precise and jitter plus-minus half a second. π
π¦ Conditions π¦
all conditions get their parameters from the context stack (or just "the context"). the only thing passed to them via perl's call mechanism is the callback itself. but conditions can totally be called without a callback too β in which case they pass further data that would otherwise land as @_ in the callback. so a condition can be called either as
readable { .. code ... }
or
&readable(); # no callback
&readable; # DANGEROUS!! same as &readable(@_)
β οΈ that second&readable;form is a trap β it's literally the same as&readable(@_), so please don't just casually write that, bestie.
you can use conditions either after an explicit export:
use IO::Lambda qw(:lambda);
lambda { ... }
or with the package syntax:
use IO::Lambda;
IO::Lambda::lambda { ... };
π‘ Note: if you know the concept of continuation-passing style, that is exactly how conditions work β except closures are used instead of continuations (shoutout to Brock Wilcox, thanks! π«Ά).
lambda()β creates a brand newIO::Lambdaobject. main character energy.io()β same aslambda. that's it. that's the whole thing.readable($filehandle, $deadline = undef)β fires either when$filehandlebecomes readable, or after$deadline. passes one argument: TRUE if the handle is readable, FALSE if time expired. ifdeadlineisundef, no timeout gets registered at all β meaning it will never be called with FALSE. ever.writable($filehandle, $deadline = undef)β exactly the same asreadable, but it fires when$filehandlebecomes writable. iconic twin behaviour.rwx($flags, $filehandle, $deadline = undef)β fires either when$filehandlesatisfies any of the conditions in$flags, or after$deadline.$flagsis a combo of three integer constants:IO_READ,IO_WRITE, andIO_EXCEPTION, which get imported by
use IO::Lambda qw(:constants);
it passes one argument β either a combination of the same IO_XXX flags reporting which conditions the handle satisfied, or 0 if time expired. if deadline is undef, no timeout is registered, i.e. it will never return 0.
timeout($deadline)β fires after$deadline. and$deadlinecannot beundef. no cap, don't even try it.tail($lambda, @parameters)β issues$lambda-> call(@parameters), then waits for$lambdato complete. sincecallcan only happen on inactive lambdas, this will fail if@parametersis non-empty and$lambdais already running. by default,tailresets the lambda if it was already finished β you can change that behaviour by fiddling with theautorestartproperty.tails(@lambdas)β fires when all objects in@lambdasare finished, and returns the collected, unordered results of the objects.tailo(@lambdas)β same astails, except the results come back ordered. because sometimes you care about the order, and sometimes you don't πany_tail($deadline, @lambdas)β fires either when all objects in@lambdasare finished, or when$deadlineexpires. returns the lambdas that were successfully executed during the allotted time. race you to the finish line πcontext @ctxβ with no parameters, returns the current context. otherwise, replaces the current context with@ctx. so it's not possible (not that it'd be practical anyway) to clear the context with this call. if you really need to, use thethis(this)syntax. π₯this $this, @ctxβ with no parameters, returns the current lambda. otherwise, replaces both the current lambda and the current context. super useful when you're juggling several lambdas, or as a convenience hack overmyvariables, like:
this lambda { ... };
this-> wait;
instead of
my $q = lambda { ... };
$q-> wait;
condition $lambda, $callback, $method, $nameβ a helper function for creating conditions, either from lambdas or from lambda constructors.
example β converting the existing getline constructor into a condition:
sub gl(&) { getline-> call(context)-> condition( shift, \&gl, 'gl') }
...
context $fh, $buf, $deadline;
gl { ... }
π Frames π
these are the functions that let you jump back to previous callback frames, to a previously saved context. time-travel vibes only.
again([$frame, [@context]])β restarts the frame with the context. if$frameis given, it jumps to the frame previously returned by arestartablecall, and resets the context to its previous state too. if@contextis given, that's used instead.
all the conditions above β excluding lambda β are restartable with an again call (check out start for restarting a lambda). so this:
context $obj1;
tail {
return if $null++;
context $obj2;
again;
};
is thus equivalent to:
context $obj1;
tail {
context $obj2;
&tail();
};
again passes the current context along to the condition. β¨
if $frame is provided, it gets treated as the result of a previous restartable call β it holds enough data to restart a different call instead of the current one. see Frames for the deets.
restartable([$name])β saves a frame.restartablecan generate a unique$nameitself if you don't pass one. all frames get deleted when a lambda is stopped. π§Ή
example:
my $counter = 0;
context lambda { $counter += 1 };
tail {
return if 12 == shift;
my $frame = restartable;
context lambda { $counter += 10 };
tail {
again($frame);
}
}
restartable records the current content on the lambda, and again switches it back β so that the again call goes to the first tail instead of the second. sneaky but so good.
delete_frame($frame)β deletes an existing saved frame. perfect for cleaning up eventual circular references (see below π).get_frame,set_frame(@frame),swap_frame(@frame)β lower-level frame accessors that save and restore all contexts.
β οΈ do NOT use these directly β it's way too easy to accidentally create circular references, where@framepoints to a callback while the callback holds a reference back to the@framevariable via closures. (thanks to Ben Tilly for bringing up the issue! π«Ά)
π Stream IO π
okay so the whole point of this module is helping you build protocols of arbitrary complexity in a clear, consequent programming style. think about how perl's low-level sysread and syswrite relate to the higher-level readline β where the latter not only handles the buffering, but also recognises $/ as the input record separator. the section above was all about the lower-level lambda I/O conditions, which are only useful for sysread and syswrite. this section is about the higher-level lambdas that relate to those low-level ones β the same way readline relates to sysread. π
okay so every function in this section hands you back the lambda that actually does the work β not unlike a class constructor returning a freshly created class instance. these functions return newly created lambdas, and weβre gonna call them lambda constructors, or just constructors. so on this page, constructors are documented as having two inputs and one output: for example, a function sysreader takes 0 parameters, always returns a new lambda, and that lambda in turn takes four parameters and returns two. it gets written like this:
# sysreader() :: ($fh,$$buf,$length,$deadline) -> ($result,$error)
since all stream I/O lambdas return the same set of scalars, that return type is gonna be called ioresult from here on:
# ioresult :: ($result, $error)
# sysreader() :: ($fh,$$buf,$length,$deadline) -> ioresult
ioresultβs first scalar is defined on success, and is not otherwise. in the latter case, the second scalar contains the error, usually either $! or 'timeout' if $deadline was set. π«Ά
before we get into the actual functions, bestie, look at the kind of code that benefits from using them. letβs take a lambda that needs to implement a very simple HTTP/0.9 request:
lambda {
my $handle = shift;
my $buf = '';
context getline, $handle, \$buf;
tail {
my $req = shift;
die "bad request" unless $req =~ m[GET (.*)$]i;
do_request($handle, $1);
}}
getline reads from $handle to $buf, and wakes up when a new line is there. but what if we need, for example, HTTPS instead of HTTP, where reading from a socket may involve some writing, and of course some waiting? then the first default parameter to getline has to be replaced. by default,
context getline, $handle, \$buf;
is the same as
my $reader = sysreader;
context getline($reader), $handle, \$buf;
where sysreader creates a lambda $reader, that given $handle, awaits when it becomes readable, and reads from it. getline, in turn, repeatedly calls $reader, until the whole line is read.
so we call
context getline(https_reader), $handle, \$buf;
instead, and that should conform to the sysreader signature:
sub https_reader
{
lambda {
my ( $fh, $buf, $length, $deadline) = @_;
# read from SSL socket
return $error ? (undef, $error) : $data;
}
}
iβm not showing the actual implementation of a HTTPS reader (if youβre curious, look at IO::Lambda::HTTP::HTTPS), but the idea is that inside that reader, it is perfectly fine to do any number of read and write operations, and wait for their completion too, as long as the upper-level lambda will sooner or later get the data. getline (or, rather, readbuf that getline is based on) wonβt care about internal states of the reader. β¨
check out t/06_stream.t that emulates reading and writing implemented in this fashion.
these functions are imported with
use IO::Lambda qw(:stream);
sysreader() :: ($fh, $$buf, $length, $deadline) -> ioresultβ creates a lambda that accepts all the parameters used bysysread(except$offsetthough), plus$deadline. the lambda tries to read$lengthbytes from$fhinto$buf, when$fhbecomes available for reading. if$deadlineexpires, it fails with'timeout'error. on successful read, it returns the number of bytes read, or$!otherwise.syswriter() :: ($fh, $$buf, $length, $offset, $deadline) -> ioresultβ creates a lambda that accepts all the parameters used bysyswriteplus$deadline. the lambda tries to write$lengthbytes to$fhfrom$buffrom$offset, when$fhbecomes available for writing. if$deadlineexpires, it fails with'timeout'error. on successful write, it returns the number of bytes written, or$!otherwise.readbuf($reader = sysreader()) :: ($fh, $$buf, $cond, $deadline) -> ioresultβ creates a lambda that is able to perform buffered reads from$fh, either using a custom lambdareader, or using one newly generated bysysreader. when called, the lambda reads continually from$fhinto$buf, and either fails on timeout, I/O error, or end of file, or succeeds if the$condcondition matches.
the condition $cond is a βsmart matchβ of sorts, and can be one of:
- integer β the lambda will succeed when
$bufis exactly$condbytes long. - regexp β the lambda will succeed when
$condmatches the content of$buf. note thatreadbufsaves and restores the value ofpos($$buf), so use of\Gis encouraged here. - coderef :: ($buf -> BOOL) β the lambda succeeds if the coderef called with
$bufreturns a true value. - undef β the lambda will succeed on end of file. note that for all other conditions, end of file is reported as an error, with the literal
"eof"string.
writebuf($writer) :: ($fh, $$buf, $length, $offset, $deadline) -> ioresultβ creates a lambda that is able to perform buffered writes to$fh, either using a custom lambdawriter, or using one generated bysyswriter. that writer lambda, in turn, writes continually$buf(from$offset,$lengthbytes) and either fails on timeout or I/O error, or succeeds when$lengthbytes are written successfully.
if $length is undefined, the buffer is continuously checked if it got new data. this feature can be used to implement concurrent writes.
getline($reader) :: ($fh, $$buf, $deadline) -> ioresultβ same asreadbuf, but succeeds when a string of bytes ended by a newline is read.
β¨ Higher-order functions β¨
these functions are literally the reason the lambda exists in IO::Lambda. theyβre named after the classic function names on purpose, and they give you a similar interface. π
these functions are imported with
use IO::Lambda qw(:func);
mapcar($lambda) :: @p -> @rβ given a$lambda, creates another lambda that accepts array@p, and sequentially executes$lambdawith each parameter from the array. the lambda returns results collected from the executed lambdas.
print mapcar( lambda { 1 + shift })-> wait(1..5);
23456
mapcar can be used for organizing simple loops:
mapcar(curry { sendmail(shift) })-> wait(@email_addresses);
filter($lambda) :: @p -> @rβ given a$lambda, creates another lambda that accepts array@p, and sequentially executes$lambdawith each parameter from the array. depending on the result of the execution, parameters are either returned, or not returned back to the caller.
print filter(lambda { shift() % 2 })-> wait(1..5);
135
fold($lambda) :: @b -> @c; $lambda :: ($a,@b) -> @cβ given a$lambda, returns another lambda that accepts array@b, and runs pairwise its members through$lambda. the results of repeated execution of$lambdaare returned.
print fold( lambda { $_[0] + $_[1] } )-> wait( 1..4 );
10
curry(@a -> $l) :: @a -> @bβcurryaccepts a function that returns a lambda, and possible parameters to it. returns a new lambda that will execute the inner lambda, and returns its result as is. for example,
context $lambda, $a, $b, $c;
tail { ... }
where $lambda accepts three parameters, can be rewritten as
$m = curry { $lambda, $a, $b };
context $m, $c;
tail { ... }
another example, tie readbuf with a filehandle and buffer:
my $readbuf = curry { readbuf, $fh, \(my $buf = '') };
seq() :: @a -> @bβ creates a new lambda that executes all lambdas passed to it in@asequentially, one after another. the lambda returns results collected from the executed lambdas.
sub seq { mapcar curry { shift }}
print seq-> wait( map { my $k = $_; lambda { $k } } 1..5);
12345
par($max = 0) :: @a -> @bβ given a limit$max, returns a new lambda that accepts lambdas in@ato be executed in parallel, but so that the number of lambdas that run simultaneously never goes higher than the limit. the lambda returns results collected from the executed lambdas.
if $max is undefined or 0, it behaves similar to a lambda version of tails, i.e., all of the lambdas are run in parallel.
the code below prints 123, then sleeps, then 456, then sleeps, then 789.
par(3)-> wait( map {
my $k = $_;
lambda {
context 0.5;
timeout { print $k, "\n" }
}
} 1..9);
π Object API π
this section lists methods of the IO::Lambda class. note that by design all lambda-style functionality is also available for object-style programming. together with the fact that lambda syntax is not exported by default, it thus leaves a place for possible implementations of user-defined syntax, either with or without lambdas, on top of the object API, without accessing the internals.
the object API is mostly targeted to developers that need to connect third-party asynchronous event libraries with the lambda interface. π¦
new($class, $start)β creates a newIO::Lambdaobject in the passive state.$startwill be called once, after the lambda gets active.watch_io($flags, $handle, $deadline, $callback, $cancel)β registers an IO event listener that calls$callbackeither after$handlesatisfies the condition of$flags(a combination ofIO_READ,IO_WRITE, andIO_EXCEPTIONbits), or after$deadlinetime is passed. if$deadlineis undef, it watches for the file handle indefinitely.
the callback is called with the first parameter as an integer set of IO_XXX flags, or 0 if the callback was timed out. other parameters, as it is the case with the other callbacks, are passed the result of the last called callback attached to the same lambda. the result of this callback will then be stored and passed on to the next callback in the same fashion.
if the event is cancelled with cancel_event, then $cancel callback is executed. the result of this callback will be stored and passed on, in the same manner as results and parameters to $callback.
watch_timer($deadline, $callback, $cancel)β registers a timer listener that calls$callbackafter$deadlinetime.watch_lambda($lambda, $callback, $cancel)β registers a listener that calls$callbackafter$lambda, anIO::Lambdaobject, is finished. if$lambdais in passive state, it is started first.is_stoppedβ reports whether the lambda is stopped or not.is_waitingβ reports whether the lambda has any registered callbacks left or not.
is_passive π«Ά
Okay so is_passive tells you if the lambda hasn't run yet. It's true when the lambda is sitting in the state right after either new or reset got called. Very "still in bed, hasn't started the day" energy π
.
is_active β¨
is_active reports if the lambda was run. That's it. She's awake, bestie.
reset π
reset cancels all watchers and switches the lambda to the passive state. If there are any lambdas that watch for this object, these will be called first. Important order, no cutting the line π.
autorestart π
If set, autorestart gives permission to watchers to reset the lambda if it becomes stopped. tail does that when needed, and other watchers are allowed to do that too. It is set by default. Lowkey generous of it πΈ.
peek π
At any given time, peek returns stored data that are either passed in by call if the lambda is in the passive state, or the stored result of execution of the latest callback. Little look inside the bag, no cap π«Ά.
start π
start starts a passive lambda. It can be used for effective restart of the whole lambda; the only requirement is that the lambda should have no pending events. Clean slate, pending-events-free, iconic β¨.
call @args π
call @args stores @args internally, to be passed on to the first callback. It only works in passive state, and croaks otherwise. If called multiple times, arguments from the previous calls are overwritten. So don't spam it and expect a little collection, bestie π.
terminate @args π
terminate @args cancels all watchers and resets the lambda to the stopped state. If there are any lambdas that watch for this object, these will be notified first. @args will be stored and available for later calls by peek. Dramatic exit, but the receipts stay in the chat π.
destroy π₯
destroy cancels all watchers and resets the lambda to the stopped state. It does the same to all lambdas the caller lambda watches after, recursively. Useful where explicit, long-lived lambdas shouldn't be subject to the global destruction, which kills objects in random order; destroy kills them in some order, at least. Not chaos-proof, but it's giving "managed group chat shutdown" π₯.
wait @args β³
wait @args waits for the caller lambda to finish, then returns the result of peek. If the object was in passive state, it calls call(@args), otherwise @args are not used. Patient bestie behavior, but only when it makes sense πΈ.
wait_for_all @lambdas π§Ί
wait_for_all @lambdas waits for the caller lambda and @lambdas to finish. It returns a collection of peek results for all objects. The results are unordered. Cute little pile, no particular arrangement β¨.
wait_for_any @lambdas π―
wait_for_any @lambdas waits for at least one lambda from the list of caller lambda and @lambdas to finish. It returns a list of finished objects. First one to the finish line gets the attention π.
yield $nonblocking = 0 π
yield $nonblocking = 0 runs one round of dispatching events. It returns 1 if there are more events in internal queues, 0 otherwise. If $NONBLOCKING is set, it exits as soon as possible, otherwise it waits for events; this feature can be used for organizing event loops without wait/run calls. Tiny event-loop snack break π₯.
run πββοΈ
run enters the event loop and doesn't exit until there are no registered events. It can be also called as package method. Main character event loop energy, periodt π.
bind $cancel, @args π
bind $cancel, @args creates an event record that contains the lambda and @args, and returns it. The lambda won't finish until this event is returned with resolve. $cancel is an optional callback that will be called when the event is cancelled; the callback is passed two parameters, the lambda and the cancelled event record.
bind can be called several times on a single lambda; each event requires individual resolve. No group-resolve shortcut, bestie π«Ά.
resolve $event β
resolve $event removes $event from the internal waiting list. If a lambda has no more events to wait, it notifies eventual lambdas that wait to the objects, and then stops.
Note that resolve doesn't provide any means to call associated callbacks, which is intentional. Not a bug, it's a boundary π
.
intercept $condition [ $state = '*' ] $coderef π΅οΈββοΈ
intercept $condition [ $state = '*' ] $coderef installs a $coderef as an overriding hook for a condition callback, where condition is tail, readable, writable, etc. Whenever a condition callback is being called, the $coderef hook will be called instead, that should be able to analyze the call, and allow or deny it the further processing.
$state, if omitted, is equivalent to '*', that means that checks on lambda state are omitted too. Setting $state to undef is allowed though, and will match when the lambda state is also undefined (which it is by default).
There can exist more than one intercept handlers, stacked on top of each other. If $coderef is undef, the last registered hook is removed.
Example:
my $q = lambda { ... tail { ... }};
$q-> intercept( tail => sub {
if ( stars are aligned right) {
# pass
return this-> super(@_);
} else {
return 'not right';
}
});
See also state, super, and override. Teamwork, bestie π.
override $condition [ $state = '*' ] $coderef π
override $condition [ $state = '*' ] $coderef installs a $coderef as an overriding hook for a condition - tail, readable, writable, etc, possibly with a named state. Whenever a lambda calls one of these condition, the $coderef hook will be called instead, that should be able to analyze the call, and allow or deny it the further processing.
$state, if omitted, is equivalent to '*', that means that checks on lambda state are omitted too. Setting $state to undef is allowed though, and will match when the lambda state is also undefined (which it is by default).
There can exist more than one override handlers, stacked on top of each other. If $coderef is undef, the last registered hook is removed.
Example:
my $q = lambda { ... tail { ... }};
$q-> override( tail => sub {
if ( stars are aligned right) {
# pass
this-> super;
} else {
# deny and rewrite result
return tail { 'not right' }
}
});
See also state, super, and intercept. Matching set, slay π.
super π¦ΈββοΈ
super is analogous to Perl's SUPER, but on the condition level. This method is designed to be called from overridden conditions to call the original condition or callback.
There is a slight difference in the call syntax, depending on whether it is being called from inside an override or intercept callback. The intercept'ed callback will call the previous callback right away, and may call it with parameters directly. The override callback will only call the condition registration routine itself, not the callback, and therefore is called without parameters. See intercept and override for examples of use. Very "know which parent you're calling" energy π
.
state $state π·οΈ
state $state is a helper function for explicit naming of condition calls. The function stores the $state string on the current lambda; this string can be used in calls to intercept and override to identify a particular condition or a callback.
The recommended use of the method is when a lambda contains more than one condition of a certain type; for example the code
tail {
tail {
...
}}
is therefore better to be written as
state A => tail {
state B => tail {
...
}}
Label your chaos, bestie π·οΈβ¨.
Exceptions and backtrace π
In addition to the normal call stack as reported by the caller builtin, it can be useful also to access execution information of the thread of events, when a lambda waits for another, which in turn waits for another, etc. The following functions deal with backtrace information and exceptions, that propagate through thread of events.
catch $coderef, $event β registers $coderef on $event, that is called when $event is aborted via either cancel_event, cancel_all_event, or terminate:
my $resource = acquire;
context lambda { .. $resource .. };
catch {
$resource-> free;
} tail {
$resource-> free;
}
catch must be invoked after a condition, but in the syntax above that means that catch should lexically come before it. If undesirable, use explicit event reference:
my $event = tail { ... };
catch { ... }, $event;
autocatch $event β prefixes a condition, so that it is called even if cancelled. However, immediately after the call the exception is rethrown. Can be used in the following fashion:
context lambda ...;
autocatch tail {
print "aborted\n" if this-> is_cancelling;
.. finalize ...
};
is_cancelling β returns true if running within a catch block.
call_again(@param) β to be called only from within a catch block. Calls the normal callback that would be called if the event wouldn't be cancelled. @param is passed to the callback.
throw(@error) β terminates the current lambda, then propagates @error to the immediate caller lambdas. They will have a chance to catch the exception with catch later, and re-throw by calling throw again. The default action is to propagate the exception further.
When there are no caller lambdas, a sigthrow callback is called ( analog: die outside eval calls $SIG{__DIE__} ).
sigthrow($callback :: ($lambda, @error)) β retrieves and sets a callback that is invoked when throw is called on lambda that no lambdas wait for. By default, is empty. When invoked, is passed the lambda, and parameters passed to throw.
callers β returns event records that watch for the lambda.
callees β returns event records that corresponds to the lambdas this lambda watches.
backtrace β returns a IO::Lambda::Backtrace object that represents thread of events which leads to the current lambda. See IO::Lambda::Backtrace for more. Receipts, but make them event-thread receipts π§π.
π MISCELLANEOUS π
π§© Included modules π§©
Okay bestie, hereβs the lil family that comes with it β each one is its own vibe:
IO::Lambda::Backtraceβ debug chains of events.IO::Lambda::Signalβ POSIX signals.IO::Lambda::Socketβ lambda versions ofconnect,accept, etc.IO::Lambda::HTTPβ implementation of HTTP and HTTPS protocols. HTTPS requiresIO::Socket::SSL, NTLM/Negotiate authentication requiresAuthen::NTLMmodules (not marked as dependencies).IO::Lambda::DNSβ asynchronous domain name resolver.IO::Lambda::SNMPβ SNMP requests lambda style. Requires SNMP.IO::Lambda::Threadβ run blocking code executed in another thread. Requires perl version greater than 5.8.0, preferably 5.10.0, built with threads.IO::Lambda::Forkβ run blocking code executed in another process context. Doesnβt work on win32 for obvious reasons.IO::Lambda::Messageβ base class for message queues over existing file handles.IO::Lambda::DBIβ asynchronous DBI.IO::Lambda::Pollβ generic polling wrapper.IO::Lambda::Flockβflock(2)wrapper.IO::Lambda::Mutexβ wait for a shared resource.
π Debugging π
Various sub-modules can be controlled with the single environment variable, IO_LAMBDA_DEBUG, which is treated as a comma-separated list of modules. For example,
env IO_LAMBDA_DEBUG=io=2,http perl script.pl
displays I/O debug messages from IO::Lambda (with extra verbosity) and from IO::Lambda::HTTP. IO::Lambda responds for the following keys:
ioβ Prints debugging information about file and timeout asynchronous events.lambdaβ Print debugging information about event flow of lambda objects, where one object waits for another, lambda being cancelled, finished, etc.callerβ Increase verbosity of lambda by storing information about which line invoked object creation and subscription. SeeIO::Lambda::Backtracefor more.dieβ If set, fatal errors dump the stack trace.loop=MODULEβ Sets loop module, one of: Select, AnyEvent, Prima, POE.
Keys recognized for the other modules: select, dbi, http, https, signal, message, thread, fork, poll, flock.
π Online information π
- Project homepage:
<http://iolambda.karasik.eu.org/> - Mailing list:
io-lambda-general at lists.sourceforge.net, thanks to sourceforge. Subscribe by visiting<https://lists.sourceforge.net/lists/listinfo/io-lambda-general>.
π Benchmarks π
So the test setup is: a single-process TCP client and server; server echoes back everything is sent by the client. 500 connections sequentially created, instructed to send a single line to the server, and destroyed.
2.4GHz x86-64 linux 1.2GHz win32
Lambda/select 0.697 7.468
Lambda/select, optimized 0.257 5.273
Lambda/AnyEvent 0.648 8.175
Lambda/AnyEvent, optimized 7.087
Raw sockets using select 0.149 4.859
POE/select, components 1.185 12.306
POE/select, raw sockets 0.382 6.233
POE/select, optimized 0.770 7.510
See benchmarking code in eg/bench.
π«Ά Apologetics π«Ά
There are many async libraries readily available from CPAN. IO::Lambda is yet another one. How is it different from the existing tools? Why use it? To answer these questions, I need to show the evolution of async libraries, to explain how they grew from simple tools to complex frameworks.
First, all async libraries are based on OS-level syscalls, like select, poll, epoll, kqueue, and Win32::WaitForMultipleObjects. The first layer provides access to exactly these facilities: there are IO::Select, IO::Epoll, IO::Kqueue etc. I wonβt go deeper into describing pros and cons for programming on this level, this should be obvious.
Perl modules of the next abstraction layer are often characterised by portability and event loops. While the modules of the first layer are seldom portable, and have no event loops, the second layer modules strive to be OS-independent, and use callbacks to ease the otherwise convoluted ways async I/O would be programmed. These modules mostly populate the βasynchronous input-output programming frameworksβ niche in the perl world. The examples are many: IO::Events, EV, AnyEvent, IO::NonBlocking, IO::Multiplex, to name a few.
Finally, thereβs the third layer of complexity, which, before IO::Lambda, had a single representative: POE (now, to the best of my knowledge, IO::Async also partially falls in this category). Modules of the third layer are based on concepts from the second, but introduce a powerful tool to help the programming of complex protocols, something that isnβt available in the second layer modules: finite state machines (FSMs). The FSMs reduce programming complexity, for example, of intricate network protocols, that are best modelled as a set of states in a logical circuit. Also, the third layer modules are agnostic of the event loop module: the programmer is (almost) free to choose the event loop backend, such as native select, Gtk, EV, Prima, or AnyEvent, depending on the nature of the task.
IO::Lambda allows the programmer to build protocols of arbitrary complexity, and is also based on event loops, callbacks, and is portable. It differs from POE in the way the FSMs are declared. Where POE requires an explicit switch from one state to another, using f.ex. post or yield commands, IO::Lambda incorporates the switching directly into the program syntax. Consider POE code:
POE::Session-> create(
inline_states => {
state1 => sub {
print "state1\n";
$_[ KERNEL]-> yield("state2");
},
state2 => sub {
print "state2\n";
},
});
and the corresponding IO::Lambda code (state1 and state2 are conditions, they need to be declared separately):
lambda {
state1 {
print "state1\n";
state2 {
print "state2\n";
}}
}
In IO::Lambda, the programming style is (deliberately) not much different from the declarative
print "state1\n";
print "state2\n";
as much as the nature of asynchronous programming allows that.
To sum up, the intended use of IO::Lambda is for areas where simple callback-based libraries require lots of additional work, and where state machines are beneficial. Complex protocols like HTTP, parallel execution of several tasks, strict control of task and protocol hierarchy β this is the domain where IO::Lambda works best.
π LICENSE AND COPYRIGHT π
This work is partially sponsored by capmon ApS.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. Periodt. π
π AUTHOR π
Dmitry Karasik, dmitry@karasik.eu.org.
I wish to thank those who helped me:
Ben Tilly for providing thorough comments to the code in the synopsis, bringing up various important issues, valuable discussions, for his patience and dedicated collaboration.
David A. Golden for discussions about names, and his propositions to rename some terms into more appropriate, such as βreadβ to βreadableβ, and βpredicateβ to βconditionβ. Rocco Caputo for optimizing the POE benchmark script. Randal L. Schwartz, Brock Wilcox, and zby@perlmonks helped me to understand how the documentation for the module could be made better.
All the good people on perlmonks.org and perl conferences, who invested their time into understanding the module.
Catch u later, bestie β this icon behavior is IO::Lambda(3) from perl v5.42.2, dated 2026-02-15. Slay and stay async. πβ¨π¦
π¦ where this page comes from
- alpine 3.24: perl-io-lambda-doc 1.34-r1 Β· GPL-1.0-or-later OR Artistic-1.0-Perl
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms