View
3.678
Download
2
Category
Preview:
DESCRIPTION
The slide shows what monads are and how I implement it in Perl.
Citation preview
Monads in Perl本間 雅洋
hiratara <hira.tara@gmail.com>
About me
d.hatena.ne.jp/hiratara
twitter.com/hiratara
working as a Perl programmer
a fan of Mathematics
a reporter of this YAPC
What are Monads?
Actually, monads are not anything special.
Category theory helps your understanding.
An arrow is a function
Str Intlength
Regex, Str, Int [Str]split
@str = split /$regex/, $str, $n
$len = length $str
Or represents a method
CGI, Str [Str]param
ClassName CGInew
$cgi = CGI->new
@values = $cgi->param($str)
Connecting two arrows
f g
*
*
*
Connecting two arrows
f g
f;g
sub f;g { g(f(@_)) }
*
*
*sub f;g { my @v = f(@_); g(@v)}
or
The 2 laws of arrows
1) Identitysub id { @_ }
fid id
id;f
f;id
2) Associativity
f g h
(f;g);h
f;(g;h)
Monad consists of Kleisli arrows
Call a diagonal arrowa Kleisli arrow
f
*
M(*)
*
M(*)
Composition of 2 Kleisli arrows
f
*
M(*)
*
M(*) M(*)
*
g
Composition of 2 Kleisli arrows
*
M(*)
*
M(*) M(*)
*
sub f>=>g { f(@_)->flat_map(\&g) }
f>=>g
The 2 laws of Kleisli arrows
(i.e. Monad laws)
1) Identitysub unit { ... }
funit unit
unit>=>f
f>=>unit
*
* * *
*
M(*)
M(*)M(*)M(*)
M(*)
2) Associativity
f g h
f>=>(g>=>h)
*
M(*)
*
M(*)
*
M(*)
*
M(*)
*
M(*)
*
M(*)
*
M(*)
*
M(*)
(f>=>g)>=>h*
M(*)
Consider >=> as“a programmable ;“
Monads are made of3 things
* * * * *
M(*) M(*) M(*) M(*) M(*)
M(M(*)) M(M(*)) M(M(*)) M(M(*)) M(M(*))
1. Nested types M
* * * * *
M(*) M(*) M(*) M(*) M(*)
M(M(*)) M(M(*)) M(M(*)) M(M(*)) M(M(*))
2. A set of arrows to wrap values
unit
unit unit unit unit
unit unit unit
* * * * *
M(*) M(*) M(*) M(*) M(*)
M(M(*)) M(M(*)) M(M(*)) M(M(*)) M(M(*))
3. The operation to lift up arrows
flat_map flat_map flat_map flat_map
flat_map flat_map flat_map flat_map
POINT: M extends values
1 “foo” $objundef
$m1$m2
$m3
$m4
$m5
*
M(*)
POINT: M extends values
1 “foo” $objundef
$m1$m2
$m3
$m4
$m5
*
M(*)
unit(1) unit(“foo”) unit(undef) unit($obj)
unit
ex). List
1 “foo” $objundef
[1] [“foo”] [undef] [$obj]
[1, 2, 3][“foo”, “bar”]
[$obj1, $obj2, $obj3]
[undef, undef]
*
[*]
[]
Monads provide extended values and
its computations
Let’s implement the List Monad
All you have to do isdefine a type and unit
and flat_map.
Implementation of List Monad
sub unit { [$_[0]] }
sub flat_map { my ($m, $f) = @_; [map { @{$f->($_)} } @$m];}
That's all we need!
Well, are you interested in monads law?
The check is left asan exercise :p
Simple usage of List monads
[3, 5]
$nroll_dice
[$n + 1, ..., $n + 6]
sub roll_dice { [map { $_[0] + $_ } 1 .. 6] }
[3, 5]roll_dice’ [4, 5, ..., 9,
6, 7, ..., 11]
flat_map
Simple usage of List monads
sub roll_dice’ { flat_map $_[0] => \&roll_dice }
When we define a monad, it comes with
2 relative functions.
1. map
[3, 5]
$ngo_to_jail
0
[0, 0]map(go_to_jail)
map
1. map
[3, 5]
$ngo_to_jail
0
[0, 0]map(go_to_jail)
mapsub map_ { my ($m, $f) = @_; flat_map $m => sub { unit($f->($_[0])) };}
2. flatten
* * * * *
M(*) M(*) M(*) M(*) M(*)
M(M(*)) M(M(*)) M(M(*)) M(M(*)) M(M(*))
flatten
M(M(M(*))) M(M(M(*))) M(M(M(*))) M(M(M(*))) M(M(M(*)))
flatten flatten flatten flatten
flattenflattenflattenflattenflatten
Implementation of flatten
M(*) *
M(M(*)) M(*)
idflat_map
flatten
Implementation of flatten
M(*) *
M(M(*)) M(*)
idflat_map
flattensub flatten { my $m = shift; flat_map $m => \&id;}
flatten() has an important role on
monads.
Reduction of terms6 3 2 8
2 89
811
19
+
+
+
flatten() plays the same roleM (M (M (M
(M (MM
(MM
M
flatten
flatten
flatten
(*))))
(*)))
(*))
(*)
Can you illustrate another example of
monads?
AnyEvent
Sample code of AEmy $cv = AE::cv;
http_get "http://yapcasia.org/2011/", sub { my ($data, $hdr) = @_; $cv->send($hdr->{'content-length'});};
print $cv->recv;
Sample code of AEmy $cv = AE::cv;
http_get "http://yapcasia.org/2011/", sub { my ($data, $hdr) = @_; $cv->send($hdr->{'content-length'});};
print $cv->recv;
$cv representsa future value.
CondVar isn’ta normal value
# !! Can’t write in this way !!
sub some_callback { my $cv = shift;
print $cv, “\n”;}
Retrieve the value from $cv
sub some_callback { my $cv = shift;
print $cv->recv, “\n”;}
Run it !
% perl ./my_great_app.plEV: error in callback (ignoring): AnyEvent::CondVar: recursive blocking wait attempted at ./my_great_app.pl line 9836
You must use cb().sub some_callback { my $cv = shift;
$cv->cb(sub { print $_[0]->recv, "\n"; });}
Use cb()sub some_callback { my $cv = shift;
$cv->cb(sub { my $cv = next_task1 $_[0]->recv; $cv->cb(sub { my $cv = next_task2 $_[0]->recv; ... }); });}
Use cb()sub some_callback { my $cv = shift;
$cv->cb(sub { my $cv = next_task1 $_[0]->recv; $cv->cb(sub { my $cv = next_task2 $_[0]->recv; $cv->cb(sub { my $cv = next_task3 $_[0]->recv; $cv->cb(sub { my $cv = next_task4 $_[0]->recv; ... }); }); }); });}
sub some_callback { my $cv = shift;
$cv->cb(sub { my $cv = next_task1 $_[0]->recv; $cv->cb(sub { my $cv = next_task2 $_[0]->recv; $cv->cb(sub { my $cv = next_task3 $_[0]->recv; $cv->cb(sub { my $cv = next_task4 $_[0]->recv; $cv->cb(sub { my $cv = next_task5 $_[0]->recv; $cv->cb(sub { my $cv = next_task6 $_[0]->recv; $cv->cb(sub { my $cv = next_task7 $_[0]->recv; $cv->cb(sub { my $cv = next_task8 $_[0]->recv; $cv->cb(sub { my $cv = next_task9 $_[0]->recv; $cv->cb(sub { my $cv = next_task10 $_[0]->recv; ... }); }); }); }); }); }); }); }); }); });}
Use cb()
A callback-hell
Coro solves the problem
Use Coro::AnyEventsub some_callback { my $cv = shift;
async { my $cv1 = next_task1($cv->recv); my $cv2 = next_task2($cv1->recv); my $cv3 = next_task3($cv2->recv); my $cv4 = next_task4($cv3->recv); ... };}
Looks perfect!
Though, Coro doessome deep magic
Organize the chaos by the monad pattern
Let’s define a type and unit and flat_map
AE::CondVar is the type# CondVar(STR)my $cv = AE::cv;$cv->send(‘Normal value’);
# CondVar(CondVar(Str))my $cvcv = AE::cv;$cvcv->send($cv);
# CondVar(CondVar(CondVar(Str)))my $cvcvcv = AE::cv;$cvcvcv->send($cvcv);
Which CondVar objsdo normal values correspond to?
We can retrieve a normal value without delay
sub unit { my @values = @_
my $cv = AE::cv; $cv->send(@values);
return $cv;}
Think about flat_map()
$_[0] *
say_hiflat_map
“Hi, $_[0]” after 2 sec
“foo”after 3 sec
Think about flat_map()
“foo”
“Hi, foo”say_hi
$name
“Hi, foo”
3 sec
2 sec
5 sec
$name->flat_map(\&say_hi)
Implementation of flat_map
sub flat_map { my ($cv, $f) = @_;
$cv->cb(sub { my @values = $_[0]->recv; my $cv = $f->(@values); ... })
return ...}
Implementation of flat_map
sub flat_map { my ($cv, $f) = @_;
my $result = AE::cv; $cv->cb(sub { my @values = $_[0]->recv; my $cv = $f->(@values); $cv->cb(sub { $result->send($_[0]->recv) }); });
return $result;}
A monad comes with map and flatten
Use the CondVar monad
* * *
CV(*) CV(*) CV(*)
flat_map
next_task1
flat_map
next_task2
$cv
∋
Use the CondVar monad
* * *
CV(*) CV(*) CV(*)
flat_map
next_task1
flat_map
next_task2
$cv
∋ sub some_callback { my $cv = shift;
$cv->flat_map(\&next_task1) ->flat_map(\&next_task2) ->flat_map(\&next_task3) ->flat_map(\&next_task4) ->...}
The CV monad has the continuation monad structure
newtype Cont r a = Cont { runCont :: (a -> r) -> r}runCont cv $ \v -> print v
$cv->cb(sub { my @v = $_[0]->recv; print @v;});
The CV monad also has the Either monad structure
data Either String a = Left String | Right a
(my $right = AE::cv)->send(“A right value”);(my $left = AE::cv)->croak(“A left value”);
Handle exceptions in flat_map
... my $result = AE::cv; $cv->cb(sub { my @r = eval { $_[0]->recv }; return $result->croak($@) if $@; my $cv = $f->(@r); ... }); ...
Left l >>= _ = Left l Right r >>= f = f r
sub fail { my @values = @_
my $cv = AE::cv; $cv->croak(@values);
return $cv;}
Define subs to handle errors
Define subs to handle errors
sub catch { ... my $result = AE::cv; $cv->cb(sub { my @r = eval { $_[0]->recv }; return $result->send(@r) if @r; my $cv = $f->($@); ... }); ...
A sample code of catchunit(1, 0)->flat_map(sub { my @v = eval { $_[0] / $_[1] }; $@ ? fail($@) : unit(@v);})->catch(sub { my $exception = shift; $exception =~ /Illegal division/ ? unit(0) # recover from errors : fail($exception); # rethrow})->flat_map(sub { ...});
Does everything go well?
NO
Concurrency
CV(*), CV(*) CV(*, *)
Concurrency
CV(*), CV(*) CV(*, *)sequence
There’re no alchemy.
CV(*, *) CV(*, *)
*, * *, *id
map
Oops!
Define it directlysub sequence { my ($cv1, $cv2) = @_;
$cv1->flat_map(sub { my @v1 = @_; $cv2->flat_map(sub { my @v2 = @_; unit(@v1, @v2); }); });}
Use nested blocks to handle more than one monad
$cv1->flat_map(sub { my @v1 = @_; $cv2->flat_map(sub { my @v2 = @_; $cv3->flat_map(sub { my @v3 = @_; $cv4->flat_map(sub { my @v4 = @_; $cv5->flat_map(sub { my @v5 = @_; ...
Back to the hell
Haskell’s do expression
do v1 <- cv1 v2 <- cv2 v3 <- cv3 v4 <- cv4 ...
return (v1, v2, v3, v4, ...)
The for comprehension
Data::Monad::Base::Sugar::for { pick \my @v1 => sub { $cv1 }; pick \my @v2 => sub { $cv2 }; pick \my @v3 => sub { $cv3 }; pick \my @v4 => sub { $cv4 }; ...
yield { @v1, @v2, @v3, @v4, ... };};
sub sequence { my ($cv1, $cv2) = @_;
$cv1->flat_map(sub { my @v1 = @_; $cv2->flat_map(sub { my @v2 = @_; unit(@v1, @v2); }); });}
ex). Better implementation of sequence
sub sequence { my ($cv1, $cv2) = @_;
Data::Monad::Base::Sugar::for { pick \my @v1 => sub { $cv1 }; pick \my @v2 => sub { $cv2 }; yield { @v1, @v2 }; };}
ex). Better implementation of sequence
Conclusion
A monad is made of a type, flat_map, unit
Consider AE::cv as a monad
CondVar monads save you from a callback hell
https://github.com/hiratara/p5-Data-Monad
Recommended