Functional Pearls 4 (YAPC::EU::2009 remix)
-
Upload
osfameron -
Category
Technology
-
view
2.362 -
download
0
Transcript of Functional Pearls 4 (YAPC::EU::2009 remix)
osfameron
Functional Pe(a)rls
Functional pe(a)rls World tour!
IPW 2008LPW 2008NWE.pm May 2009YAPC::EU Lisbon 2009
version 4Osfameron
Functional Programming
What is it?
Functional Programming
About functions?
Functional Programming
About
functions?(like Object-oriented is about objects?)
Functional Programming
everything's a function!
Functional Programming
1 + 2
Functional Programming
1 + 2
Spot the
function?
Functional Programming
1 + 2
Spot the
function?
Functional Programming
1 + 2
first class?
Functional Programming
1 + 2
\+
Functional Programming
1 + 2
op
OPERATORS UNITE!
STOP this discrimation now!
What now?
Protest!
Cry!
Steal from Haskell!
What now?
Steal from Haskell!
(+) ref to add
Wrapping operators
sub op ($op) {
return
sub ($left, $right) {
eval $left $op $right;
};
}
Wrapping operators
my $add = op('+');
$add->(1, 2); # 3
YAY!
YAY?
Still uglier than Haskell (+)
op('+')
Devel::Declare
New syntax!
(But better than source filters)
Devel::Declare
New syntax!
(But better than source filters)Method declaration
MooseX::Declare
Sub::Auto
Sub::Section
on github
Gives nice syntax, using Devel::Declare
op(+)
Sub::Section
on github
Gives nice syntax, using Devel::Declare
op (Bwahaha!) (+)
Devel::Declare custom parser hook can inject code etc.
Sub::Section
on github
Gives nice syntax, using Devel::Declare
op (Bwahaha!) ('+')
Sub::Section
on github
Gives nice syntax, using Devel::Declare
Op('+') # Perl is none the wiser
Tee hee!
Devel::Declare
In Perl!
(With a bit of scary XS magic)hooks into the compiler
changing the source as you compile
horrible perl tricks to get methods installed, and braces closed
mst++, rafl++
Sections
Sections
(+) isn't really a section
Sections
(+) isn't really a section
(+1) is
Sections
(+) 10, 5 # 15
(+1) 8 # 9
Currying
Partial application
Currying
Partial application
1 arg at a time
Currying
sub add ($left, $right) {
return $left + $right;
}
Currying
sub add ($left, $right) {
return $left + $right;
}
(That's 2 arguments)
Curried functions
sub add ($left, $right) {
return $left + $right;
}
(That's 2 arguments)
add(5) ...
Curried functions
sub add ($left, $right) {
return $left + $right;
}
(That's 2 arguments)
add(5) # $left is bound to 5
Curried functions
sub add ($left, $right) {
return $left + $right;
}
(That's 2 arguments)
add(5) # $left is bound to 5
->(6); # 11
Implement in Perl
Quite simple to do with closures:
sub add {
my $left = shift;
return sub {
my $right = shift;
return $left + $right;
}
}
Implement in Perl
Quite simple to do with closures:
sub add {
my $left = shift;
return sub {
my $right = shift;
return $left + $right;
}
}
Not pretty or convenient though
Sub::Curried
on CPAN
Gives nice syntax, using Devel::Declare
sub add ($left, $right) {
return $left + $right;
}
Sub::Curried
on CPAN
Gives nice syntax, using Devel::Declare
curry add ($left, $right) {
return $left + $right;
}
Sub::Curried
on CPAN
Gives nice syntax, using Devel::Declare
curry (bwahaha!) add ($left, $right) {
return $left + $right;
}
Sub::Curried
Turn into something like
curry add {
return ROUTINE unless @_;
check_args(2, @_);
my $f = sub {
my $f = sub {
...
}
$f = $f->(shift) for @_;
return $f;
}
Sub::Curried
Turn into something like
curry add {
return ROUTINE unless @_;
check_args(2, @_);
my $f = sub {
my $f = sub {
...
}
$f = $f->(shift) for @_;
return $f;
}
Sub::Curried
add(5,6); # 11
Sub::Curried
add(5,6); # 11
add(5); # function that adds 5
Sub::Curried
add(5,6); # 11
add(5); # function that adds 5
add(); # function that adds...
Sub::Curried
add(5,6); # 11
add(5); # function that adds 5
add(); # function that adds...
i.e = \&add
Sub::Curried
Turn into something like
curry add {
return ROUTINE unless @_;
check_args(2, @_);
my $f = sub {
my $f = sub {
...
}
$f = $f->(shift) for @_;
return $f;
}
Sub::Curried
Give diagnostics if called with too many arguments
Sub::Curried
Turn into something like
curry add {
return ROUTINE unless @_;
check_args(2, @_);
my $f = sub {
my $f = sub {
...
}
$f = $f->(shift) for @_;
return $f;
}
Sub::Curried
Handleadd(1,2)
add(1)->(2)
Why?
Partial application
Why?
Automatic partial application everywhere
Why?
Automatic partial application everywhere
... turns out to be useful/elegant in Haskell
Why?
Automatic partial application everywhere
... turns out to be useful/elegant in Haskell
(actually, similar techniques are used in Perl)
Currying the Invocant
package My::Class
use base 'Class::Accessor';
__PACKAGE__->add_accessor('foo');
__PACKAGE__->add_accessor('bar');
__PACKAGE__->add_accessor('baz');
Currying the Invocant
package My::Class
use base 'Class::Accessor';
My::Class->add_accessor('foo');
My::Class->add_accessor('bar');
My::Class->add_accessor('baz');
Currying the Invocant
package My::Class
use base 'Class::Accessor';
My::Class->add_accessor('foo');
My::Class->add_accessor('bar');
My::Class->add_accessor('baz');
sub add_accessor {
my ($self, $accessor) = @_;
....
Currying the Invocant
Perl importing*{$CALLER::has} = \&has;
Currying the Invocant
Perl importing*{$CALLER::has} = \&has;
*{$CALLER::has} = mk_has();
Currying the Invocant
Perl importing*{$CALLER::has} = \&has;
*{$CALLER::has} = mk_has();
*{$CALLER::has} = has($CALLER);
Currying the Invocant
Moose!package My::Class;
use Moose; # imports has=has('My::Class')
has 'foo';
has 'bar';
has 'baz';
Currying the Invocant
Moose!package My::Class;
use Moose; # imports has=has('My::Class')
has 'foo';
has 'bar';
has 'baz';
(handwave)
Where were we?
my $add = \+; # FAIL
Where were we?
my $add = \+; # FAIL
my $add = op(+); # YAY
Where were we?
my $add = \+; # FAIL
my $add = op(+); # YAY
my $add2 = op(+2); # take THAT, Haskell!
But...
(-1)
But...
(-1)
(1-)
But...
(-1)
(1-) # minus(1)
But...
(-1) # minus(???)->(1)
(1-) # minus(1)
But...
(-1) # (flip(minus))->(1)
(1-) # minus(1)
Flip
curry flip ($fn, $left, $right) {
$fn->($right, $left);
}
Flip
curry flip ($fn, $left, $right) {
$fn->($right, $left);
}
Minus (3, 1) # 2
flip(minus)->(3, 1) # -2;
Flip
and with currying
my $prev = flip(minus)->(1);
$prev->(5);$left = 1
$right = 5
minus($right=5, $left=1); # 4
Sub::Section
More cool stuff
Sub::Section
my $greet = op(Hello .);
Sub::Section
my $greet = op(Hello .);
$greet->(World); # Hello World
Sub::Section
map op(*2), @numbers; (1,2,3,4)
> (2,4,6,8)
Sub::Section
map op(*2), @numbers; (1,2,3,4)
> (2,4,6,8)
Sub::Section
Perl map doesn't take functions...
Sub::Section
Perl map doesn't take functions...
map op(*2)->($_), @numbers; (1,2,3,4)
> (2,4,6,8)
(rant)
Perl's map is not functional
(rant)
Perl's map is not functionalWe took a feature from FP...
and made it not take functions.
(rant)
Perl's map is not functional
Perl's $_ is a horrible hack around not doing currying properly
(rant)
Perl's map is not functional
Perl's $_ is a horrible hack around not doing currying properly
Why am I still programming this silly language? ;-)
(yay!)
Perl's map is not functional
Perl's $_ is a horrible hack around not doing currying properly
Why am I still programming this silly language? ;-)
Oh yes! - because I can change it.
TODO: Functional::Map
fmap \&some_func, @list;
fmap sub { }, @list;
fmap op(+2), @list;
TODO: Functional::Map
fmap \&some_func, @list;
fmap sub { }, @list;
fmap op(+2), @list;
(those will all work anyway)
TODO: Functional::Map
fmap \&some_func, @list;
fmap sub { }, @list;
fmap op(+2), @list;
(those will all work anyway)
fmap { uc } @list;
TODO: Functional::Map
fmap \&some_func, @list;
fmap sub { }, @list;
fmap op(+2), @list;
(those will all work anyway)
fmap (bwahahaha!){ uc } @list;
TODO: Functional::Map
fmap \&some_func, @list;
fmap sub { }, @list;
fmap op(+2), @list;
(those will all work anyway)
fmap sub { uc }, @list;
TODO: Functional::Map
fmap \&some_func, @list;
fmap sub { }, @list;
fmap op(+2), @list;
(those will all work anyway)
fmap underscorify( sub { uc }),
@list;
TODO: Functional::Map
Dear lazyYAPC...
Sub::Section
my $contains_foo = op(=~/foo/);
More complex examples
centigrade = (fahrenheit-32)*5/9
More complex examples
centigrade = (fahrenheit-32)*5/9
my $f2c =
sub {
op(*5/9)->(
op(-32)->(shift))
};
More complex examples
centigrade = (fahrenheit-32)*5/9
my $f2c =
sub {
op(*5/9)->(
op(-32)->(shift))
};
MY EYES!
More complex examples
centigrade = (fahrenheit-32)*5/9
my $f2c =
sub {
op(*5/9)->(
op(-32)->(shift))
};
More complex examples
centigrade = (fahrenheit-32)*5/9
my $f2c =
sub {
op(*5/9)->(
op(-32)->(shift))
};
Never mind the boilerplate...
More complex examples
centigrade = (fahrenheit-32)*5/9
my $f2c =
op(*5/9) ();
Monadic programming is impractical in Perl...
only because of syntactic issues Mark Jason Dominus
http://perl.plover.com/classes/fp/samples/slide027.html
Monads made pretty
Source filters!http://sleepingsquirrel.org/monads/monads.html
Monads made pretty
Source filters!http://sleepingsquirrel.org/monads/monads.html
Source tree manipulation (B::OP magic)
Deparse and source text munging
Monads made pretty
We want a syntax like
mdo {
my $x = mbind(1);
my $y = mbind(2);
my $z = mbind($x + $y);
say $x * $y = $z;
}
Monads made pretty
We want a syntax like
mdo {
my $x = mbind(1);
my $y = mbind(2);
my $z = mbind($x + $y);
say $x * $y = $z;
}
mdo introduces the block
mbind gives us a hook to rotate around
Optree munging
19: my $x o
t left_shift[t3] vK ->u
o padsv[$x:2078,2080]
sM/LVINTRO ->p
s entersub[t2] sKS/TARG,3 ->t
- ex-list sK ->s
p pushmark s ->q
q const(IV 2) sM ->r
- ex-rv2cv sK/130 ->-
r gv(*Just) s ->s
u nextstate(main 2079 b.pl:20)
v:*,&,$ ->v
# : mbind (Just 2), sub { my $x = shift; ... };
nextstate(main b.pl:) v:*,&,{,$ ->
list K ->
pushmark s ->
entersub[t2] KS/TARG,3 ->
- ex-list K ->
pushmark s ->
entersub[t1] lKMS/NO(),TARG,INARGS,3 ->
- ex-list lK ->
pushmark s ->
const(IV 2) sM ->
- ex-rv2cv sK/130 ->-
gv(*Just) s ->
- ex-rv2cv sK/2 ->-
# mbind instead of >>
gv(*mbind) s ->
refgen K/1 ->
- ex-list lKRM ->
pushmark sRM ->
anoncode[CV ] lRM ->
# ??? set up anon sub
# my $x = shift
padsv[$x:2078,2080] sM/LVINTRO ->p
# the next ; is moved into this new lambda!
nextstate(main 2079 b.pl:20) v:*,&,$ ->v
Optree munging
19: my $x o
t left_shift[t3] vK ->u
o padsv[$x:2078,2080]
sM/LVINTRO ->p
s entersub[t2] sKS/TARG,3 ->t
- ex-list sK ->s
p pushmark s ->q
q const(IV 2) sM ->r
- ex-rv2cv sK/130 ->-
r gv(*Just) s ->s
u nextstate(main 2079 b.pl:20)
v:*,&,$ ->v
# : mbind (Just 2), sub { my $x = shift; ... };
nextstate(main b.pl:) v:*,&,{,$ ->
list K ->
pushmark s ->
entersub[t2] KS/TARG,3 ->
- ex-list K ->
pushmark s ->
entersub[t1] lKMS/NO(),TARG,INARGS,3 ->
- ex-list lK ->
pushmark s ->
const(IV 2) sM ->
- ex-rv2cv sK/130 ->-
gv(*Just) s ->
- ex-rv2cv sK/2 ->-
# mbind instead of >>
gv(*mbind) s ->
refgen K/1 ->
- ex-list lKRM ->
pushmark sRM ->
anoncode[CV ] lRM ->
# ??? set up anon sub
# my $x = shift
padsv[$x:2078,2080] sM/LVINTRO ->p
# the next ; is moved into this new lambda!
nextstate(main 2079 b.pl:20) v:*,&,$ ->v
KABOOM
Source deparsing
Works surprisingly well
Source deparsing
Works surprisingly well
for trivial cases
(a bit fragile)
Source deparsing
Works surprisingly well
for trivial cases
(a bit fragile)
though localised to mdo { ... }
Devel::Declare
Even nicer syntax
mdo {
mbind $x = 1;
mbind $y = 2;
mbind $z = $x + $y;
say $x * $y = $z;
}
Devel::Declare
And cuter implementation:
mdo {
mbind $x = 1;
mbind $y = 2;
mbind $z = $x + $y;
say $x * $y = $z;
}
Devel::Declare
The problem:
mdo {
mbind 1, sub { my $x = shift;
mbind 2, sub { my $y = shift;
mbind $x + $y,
sub { my $z = shift;
say $x * $y = $z;
}
...
Devel::Declare
No need to count nesting:
scope_inject
B::Hooks::EndOfScope
Use to inject a ; at the end of method declarations:
method foo ($x) {
print $x;
} # look Ma, no semicolon!
Devel::Declare
mbind's scope_inject adds a }
mdo {
mbind 1, sub { my $x = shift;
mbind 2, sub { my $y = shift;
mbind $x + $y,
sub { my $z = shift;
say $x * $y = $z;
} # adds a closing brace
}
Devel::Declare
mbind's scope_inject adds a }
mdo {
mbind 1, sub { my $x = shift;
mbind 2, sub { my $y = shift;
mbind $x + $y,
sub { my $z = shift;
say $x * $y = $z;
}
} # adds a closing brace
}
Devel::Declare
mbind's scope_inject adds a }
mdo {
mbind 1, sub { my $x = shift;
mbind 2, sub { my $y = shift;
mbind $x + $y,
sub { my $z = shift;
say $x * $y = $z;
}
}
} # adds a closing brace
}
Devel::Declare
mbind's scope_inject adds a }
mdo {
mbind 1, sub { my $x = shift;
mbind 2, sub { my $y = shift;
mbind $x + $y,
sub { my $z = shift;
say $x * $y = $z;
}
}
}
} # closes block
So...
We can now sequence commands!
mdo {
mbind $x = 1;
mbind $y = 2;
mbind $z = $x + $y;
say $x * $y = $z;
}
So...
We can now sequence commands!
in pure Perl!
So...
OK, so this was big news in Haskell in 1990s
So...
OK, so this was big news in Haskell in 1990s
Imperative languages have always done this
What else can monads do ?
Sequencing
mdo {
mbind $x = 1;
mbind $y = 2;
mbind $z = $x + $y;
say $x * $y = $z;
}
What else can monads do ?
Sequencing
mdo {
mbind $x = 1;
mbind $y = 2;
mbind $z = $x + $y;
say $x * $y = $z;
}
What else can monads do ?
Sequencing
mdo {
mbind $x = 1;
mbind $y = 2;
mbind $z = $x + $y;
say $x * $y = $z;
}
What else can monads do ?
Sequencing
mdo {
mbind $x = 1;
mbind $y = 2;
mbind $z = $x + $y;
say $x * $y = $z;
}
Programmable semicolon!
Maybe
Success/Failure
mdo (Maybe) {
mbind $FH = m_open('