Monads in perl

Post on 08-May-2015

3.673 views 2 download

description

The slide shows what monads are and how I implement it in Perl.

Transcript of Monads in perl

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