Sub::Lambda - syntactic sugar for lambdas in Perl
use Sub::Lambda; *plus = fn a => fn b => '$a + $b'; my $minus = fn a => fn b => q{ $a - $b }; *flip = fn f => fn a => fn b => ap qw(f b a); *sum = fn h => -t => q{ @t ? $h+sum(@t) : ($h || 0) }; print plus(1)->(2) . "\n"; # 3 = 1 + 2 print $minus->(10)->(5) . "\n"; # 5 = 10 - 5 print flip($minus)->(10)->(5) . "\n"; # -5 = 5 - 10 print sum(1,2,3,4) . "\n"; # 10 = 1+2+3+4 my $fac = fn f => fn n => q{ ($n<1) ? 1 : $n*$f->($n-1) }; my $Y = fn m => ap( (fn f => ap m => fn a => ap f => f => a => ()) => (fn f => ap m => fn a => ap f => f => a => ()) ); print $Y->($fac)->(5) . "\n"; # 120 = 5!
This module provides syntactic sugar for lambda abstractions and applications. Perl supports lambdas through subroutine references. You can write things like the curried addition:
sub { my ($x) = @_; sub { my ($y) = @_; $x + $y } }
However, this is not very convenient nor readable for more involved lambda expressions. Contrast this with the sugared syntax for the same function:
fn x => fn y => q{ $x + $y }
If you would like even more convenience at the expense of somewhat unclear semantics, check out the experimental Sub::Lambda::Filter module, with which you could write:
( \x -> \y -> {$x+$y} )
Models lambda abstraction. In list context, outputs the Perl code for a lambda with a given pattern and body. In scalar context, returns a subroutine reference. This context trick allows the sub to be compiled in a one-step eval, which appears to be necessary to make sure Perl gets the variable scoping right. Note that this means the end user has to make sure that the functions are called in a scalar context! When in doubt, use scalar()
.
The basic pattern is just a single variable name; an incrementor can be written as:
fn(x => '$x + 1')->(1) # =2
Prefixing with a dash captures lists of arguments:
fn(-x => 'scalar(@x)')->(1,2,3,4,5) #= 5
Multiple arguments are allowed too:
(fn qw(a b) => '$a+$b')->(1, 2) #= 3 (fn qw(h -t) => '{$h=>[@t]}')->(1,2,3,4) #= {1 => [2,3,4]}
Currying is possible too:
(fn a => fn b => '$a+$b')->(1)->(2) #= 3
Here are some translation examples:
Scheme Perl (lambda (x) (f x)) (fn x => 'f($x)') (lambda x (f x)) (fn -x => 'f(\@x)') (lambda (x y z) (f x y z)) (fn x => y => z => q{ f($x,$y,$z) }) (lambda (h . t) (f h t)) (fn h => -t => q{ f($h, \@t) }) ... Haskell \f -> \a -> \b -> f b a (fn f=>fn a=>fn b=>q{$f->($b)->($a)})
AVOID nesting in the following way:
fn a => q{ fn b => '$a+$b' }
In this example $a+$b
will be compiled outside of the lexical scope where a
was defined, hence the function will not work.
Models application. Applies the given expressions to the left, as if with Haskell foldl1 ($)
. In list context, it generates Perl code, while in scalar context it eval's it:
print ap(qw(a b c)) # ($a)->($b)->(scalar($c));
The expressions can be pieces of Perl code or neat variable names (x
standing for $x
and -x
for @x
).
ap
is useful as a shorthand in cases like this:
fn f => fn a => fn b => q{ $f->($b)->($a) }
Expressed with ap
it reads:
fn f => fn a => fn b => ap qw(f b a)
With ap and parentheses one can write arbitrarily complex lambda expressions.
To take a large example, let us write down the applicative-order Y combinator and a factorial function (for derivation and rationale see Richard P. Gabriel's The Why of Y, available online http://www.dreamsongs.com/NewFiles/WhyOfY.pdf).
In pseudo-Haskell (Haskell's type system prohibits Y):
Y = \m -> (\f -> m (\a -> f f a)) (\f -> m (\a -> f f a))
In Perl with Sub::Lambda:
my $Y = fn m => ap( (fn f => ap m => fn a => ap qw(f f a)) => (fn f => ap m => fn a => ap qw(f f a)) );
In standard Perl (compare with the above):
my $Y = sub { my ($m) = @_; ( sub { my ($f) = @_; $m->(sub { my ($a) = @_; $f->($f)->($a); }) } )->( sub { my ($f) = @_; $m->(sub { my ($a) = @_; $f->($f)->($a); })})};
Factorial function:
my $fac = fn f => fn n => q{ ($n<1) ? 1 : $n*$f->($n-1) };
Testing it:
print $Y->($fac)->(5); # prints 120=5!
To avoid repeated compilations fn is memoized. This only improves performance where lambdas are constructed repeatedly, as in loops. The performance gains are modest.
fn ap
Anton Tayanovskyy <anton.tayanovskyy at gmail.com>
Big thanks to Eugene Grigoriev <eugene.grigoriev at gmail.com> for his help, ideas and feedback.
Copyright 2008 Anton Tayanovskyy. All rights reserved.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.