Slide 1

Slide 1 text

Locked in a Box: More Closures in Perl5 & Raku Steven Lembark Workhorse Computing [email protected]

Slide 2

Slide 2 text

Closures are simple enough A piece of code ‘closes over’ some local variable(s). They require first-class subs (a’la LISP)

Slide 3

Slide 3 text

Closures are simple enough A piece of code ‘closes over’ some local variable(s). They require first-class subs (a’la LISP, Perl, or Raku).

Slide 4

Slide 4 text

Closures are simple enough A piece of code ‘closes over’ some local variable(s). They require first-class subs (a’la LISP, Perl, or Raku). Utility from: Encapsulatoin: not knowing what’s inside. Generated dynamically. All you can do is execute them.

Slide 5

Slide 5 text

Why we care Flexibility allows more reusable code. Division of labor: Stable framework. Dynamic closures. Frameworks are bulky with exceptions, error handling. Closures are lightweght for repeated decisions.

Slide 6

Slide 6 text

Perl5 closure: Object Trampoline When having not the object you want is what you need. Flyweight pattern: Replace a lightweight object at runtime. Caller has no idea of the replacement. Only pass through flyweight once.

Slide 7

Slide 7 text

Creating a flyweight Flyweight for Heavy::Weight class. my $hvywt = Heavy::Weight->create( $foo );

Slide 8

Slide 8 text

Creating a flyweight Flyweight for Heavy::Weight class. Object::Trampoline as class. my $hvywt = Heavy::Weight ->create( $foo ); my $hvywt = Object::Trampoline->create( 'Heavy::Weight', $foo );

Slide 9

Slide 9 text

Creating a flyweight Flyweight for Heavy::Weight class. Push Object::Trampoline into class. Bumps the original class into the first argument. my $hvywt = Heavy::Weight ->create( $foo ); my $hvywt = Object::Trampoline->create( 'Heavy::Weight', $foo );

Slide 10

Slide 10 text

Creating a flyweight Flyweight for Heavy::Weight class. Push Object::Trampoline into class. Bumps the original class into the first argument. The method name remains unchanged. my $hvywt = Heavy::Weight ->create( $foo ); my $hvywt = Object::Trampoline->create( 'Heavy::Weight', $foo );

Slide 11

Slide 11 text

Using a flyweight Construct the flyweight. my $dict = Object::Trampoline->create ( 'Lang::Translate', 'Trad Manderin' ); # dict is O::T

Slide 12

Slide 12 text

Using a flyweight Call any method other than DESTROY. The object is replaced. $dict->blessed returns “Lang::Translate” my $dict = Object::Trampoline->create ( 'Lang::Translate', 'Trad Manderin' ); say $dict->blessed; # ”Lang::Translate”

Slide 13

Slide 13 text

O::T Structure Object has no methods. AUTOLOAD as constructor. our $handler = ‘Object::Trampoline::Bounce’; our $AUTOLOAD = ‘’; AUTOLOAD { my ( undef, $proto, @argz ) = @_; my $name = ( split '::' => $AUTOLOAD )[ -1 ]; bless sub { $proto->$name( @argz ) }, $handler }

Slide 14

Slide 14 text

O::T Structure Object has no methods. Closure executes the ‘real’ constructor’. our $handler = ‘Object::Trampoline::Bounce’; our $AUTOLOAD = ‘’; AUTOLOAD { my ( undef, $proto, @argz ) = @_; my $name = ( split '::' => $AUTOLOAD )[ -1 ]; bless sub { $proto->$name( @argz ) }, $handler }

Slide 15

Slide 15 text

O::T Structure Object has no methods. Blessed into handler class to manage replacement. our $handler = ‘Object::Trampoline::Bounce’; our $AUTOLOAD = ‘’; AUTOLOAD { my ( undef, $proto, @argz ) = @_; my $name = ( split '::' => $AUTOLOAD )[ -1 ]; bless sub { $proto->$name( @argz ) }, $handler }

Slide 16

Slide 16 text

Bouncing the object @_ is passed by reference. our @ISA = (); our $AUTOLOAD = ''; AUTOLOAD { $_[0] = $_[0]->(); my $name = ( split '::' => $AUTOLOAD )[ -1 ]; my $obj = shift; $obj->$name( @_ ) } DESTROY{}

Slide 17

Slide 17 text

Bouncing the object @_ is passed by reference. $_[0] references the caller’s value. our @ISA = (); our $AUTOLOAD = ''; AUTOLOAD { $_[0] = $_[0]->(); my $name = ( split '::' => $AUTOLOAD )[ -1 ]; my $obj = shift; $obj->$name( @_ ) } DESTROY{}

Slide 18

Slide 18 text

Bouncing the object Executing $_[0] calls a constructor. our @ISA = (); our $AUTOLOAD = ''; AUTOLOAD { $_[0] = $_[0]->(); my $name = ( split '::' => $AUTOLOAD )[ -1 ]; my $obj = shift; $obj->$name( @_ ) } DESTROY{}

Slide 19

Slide 19 text

Bouncing the object Executing $_[0] calls a constructor. Assigning $_[0] replaces the caller’s value. our @ISA = (); our $AUTOLOAD = ''; AUTOLOAD { $_[0] = $_[0]->(); my $name = ( split '::' => $AUTOLOAD )[ -1 ]; my $obj = shift; $obj->$name( @_ ) } DESTROY{}

Slide 20

Slide 20 text

Bouncing the object Shifting leaves the stack with arguments. our @ISA = (); our $AUTOLOAD = ''; AUTOLOAD { $_[0] = $_[0]->(); my $name = ( split '::' => $AUTOLOAD )[ -1 ]; my $obj = shift; $obj->$name( @_ ) } DESTROY{}

Slide 21

Slide 21 text

Bouncing the object Shifting leaves the stack with arguments. Dispatched via the newly-minted object. our @ISA = (); our $AUTOLOAD = ''; AUTOLOAD { $_[0] = $_[0]->(); my $name = ( split '::' => $AUTOLOAD )[ -1 ]; my $obj = shift; $obj->$name( @_ ) } DESTROY{}

Slide 22

Slide 22 text

Bouncing the object Stub DESTROY avoids constructing at END. our @ISA = (); our $AUTOLOAD = ''; AUTOLOAD { $_[0] = $_[0]->(); my $name = ( split '::' => $AUTOLOAD )[ -1 ]; my $obj = shift; $obj->$name( @_ ) } DESTROY{}

Slide 23

Slide 23 text

Bouncing the object Stub DESTROY avoids constructing at END. Empty @ISA avoids calling UNIVERSAL. our @ISA = (); our $AUTOLOAD = ''; AUTOLOAD { $_[0] = $_[0]->(); my $name = ( split '::' => $AUTOLOAD )[ -1 ]; my $obj = shift; $obj->$name( @_ ) } DESTROY{}

Slide 24

Slide 24 text

Using a module Object::Trampoiline::Use does a “use $module”. Slightly larger closure to eval “use $module”. Blessed into the same handler class: The closure encapsulates the use. Bounce doesn’t know it happens.

Slide 25

Slide 25 text

Flyweight closure Closure is dynamic. Encapsulates the ‘real’ constructor. Delays construction until necessary at runtime. Simplifies the boilerplate: Object encapsulates dynamic behavior. Class handles boilerplate.

Slide 26

Slide 26 text

Raku's closures are intrinsically no more powerful than Perl's or Scheme's. However, they are (in most cases) vastly easier to use, due to the sheer number of different ways you can specify them. - Damian Conway

Slide 27

Slide 27 text

Raku closures More than one way to do it... my $LIMIT = get_limit(); $test = sub ($x) { $x < $LIMIT }; $test = -> $x { $x < $LIMIT }; $test = { $^x < $LIMIT }; $test = { $_ < $LIMIT }; $test = * < $LIMIT ; # and later... if ($data ~~ $test) {...}

Slide 28

Slide 28 text

Raku closures All of these close over $LIMIT. my $LIMIT = get_limit(); $test = sub ($x) { $x < $LIMIT }; $test = -> $x { $x < $LIMIT }; $test = { $^x < $LIMIT }; $test = { $_ < $LIMIT }; $test = * < $LIMIT ; # and later... if ($data ~~ $test) {...}

Slide 29

Slide 29 text

Raku closures Argument as: Signature: $x my $LIMIT = get_limit(); $test = sub ($x) { $x < $LIMIT }; $test = -> $x { $x < $LIMIT }; $test = { $^x < $LIMIT }; $test = { $_ < $LIMIT }; $test = * < $LIMIT ; # and later... if ($data ~~ $test) {...}

Slide 30

Slide 30 text

Raku closures Argument as: Signature: $x Autovar: $^x my $LIMIT = get_limit(); $test = sub ($x) { $x < $LIMIT }; $test = -> $x { $x < $LIMIT }; $test = { $^x < $LIMIT }; $test = { $_ < $LIMIT }; $test = * < $LIMIT ; # and later... if ($data ~~ $test) {...}

Slide 31

Slide 31 text

Raku closures Argument as: Signature: $x Autovar: $^x Default: $_ my $LIMIT = get_limit(); $test = sub ($x) { $x < $LIMIT }; $test = -> $x { $x < $LIMIT }; $test = { $^x < $LIMIT }; $test = { $_ < $LIMIT }; $test = * < $LIMIT ; # and later... if ($data ~~ $test) {...}

Slide 32

Slide 32 text

Raku closures Argument as: Signature: $x Autovar: $^x Default: $_ Whatever * my $LIMIT = get_limit(); $test = sub ($x) { $x < $LIMIT }; $test = -> $x { $x < $LIMIT }; $test = { $^x < $LIMIT }; $test = { $_ < $LIMIT }; $test = * < $LIMIT ; # and later... if ($data ~~ $test) {...}

Slide 33

Slide 33 text

Raku closures Smartmatch passes $data to the subs. my $LIMIT = get_limit(); $test = sub ($x) { $x < $LIMIT }; $test = -> $x { $x < $LIMIT }; $test = { $^x < $LIMIT }; $test = { $_ < $LIMIT }; $test = * < $LIMIT ; # and later... if ($data ~~ $test) {...}

Slide 34

Slide 34 text

Raku Trees Simple binary tree. class Tree { has $.data is rw; has $.left; has $.rite; method traverse ( &handler ) { .traverse( &handler ) with $.left; node_action( self ); .traverse( &handler ) with $.rite; } }

Slide 35

Slide 35 text

Raku Trees Tree Itself: data and child branches. class Tree { has $.data is rw; has $.left; has $.rite; }

Slide 36

Slide 36 text

Raku Trees Method has implicit object argument. class Tree { method traverse ( &handler ) { .traverse( &handler ) with $.left; handler( self ); .traverse( &handler ) with $.rite; } }

Slide 37

Slide 37 text

Raku Trees “with” assigns value, short- circuits on undef. class Tree { method traverse ( &handler ) { .traverse( &handler ) with $.left; handler( self ); .traverse( &handler ) with $.rite; } }

Slide 38

Slide 38 text

Raku Trees $.left and $.rite use the method’s implicit object. class Tree { method traverse ( &handler ) { .traverse( &handler ) with $.left; handler( self ); .traverse( &handler ) with $.rite; } }

Slide 39

Slide 39 text

Raku Trees Requires a code object via “&” in argument. class Tree { method traverse ( &handler ) { .traverse( &handler ) with $.left; handler( self ); .traverse( &handler ) with $.rite; } }

Slide 40

Slide 40 text

Raku Trees &handler passes the object. class Tree { method traverse ( &handler ) { .traverse( &handler ) with $.left; handler( self ); .traverse( &handler ) with $.rite; } }

Slide 41

Slide 41 text

Raku Trees handler() dispatches the code object. class Tree { method traverse ( &handler ) { .traverse( &handler ) with $.left; handler( self ); .traverse( &handler ) with $.rite; } }

Slide 42

Slide 42 text

Raku Trees Let’s create a tree... my $tree = Tree.new: :data( 1 ) , :left ( Tree.new: data => 2 , :left ( Tree.new: data => -6 ) ) , :rite ( Tree.new: :data( 3 ) , :left ( Tree.new: data => -4 ) , :rite ( Tree.new: data => 5 ) )

Slide 43

Slide 43 text

Raku Trees Assign a value data left rite :foo() format calls a constructor. “foo => X” supplies a value. my $tree = Tree.new: :data( 1 ) , :left ( Tree.new: data => 2 , :left ( Tree.new: data => -6 ) ) , :rite ( Tree.new: :data( 3 ) , :left ( Tree.new: data => -4 ) , :rite ( Tree.new: data => 5 ) )

Slide 44

Slide 44 text

Raku Trees Let’s create a tree... 1 / \ 2 3 / / \ -6 -4 5 my $tree = Tree.new: :data( 1 ) , :left ( Tree.new: data => 2 , :left ( Tree.new: data => -6 ) ) , :rite ( Tree.new: :data( 3 ) , :left ( Tree.new: data => -4 ) , :rite ( Tree.new: data => 5 ) )

Slide 45

Slide 45 text

Raku Trees Let’s create a tree... 1 / \ 2 3 / / \ -6 -4 5 transform() sees data as: -6 2 1 -4 3 5 my $tree = Tree.new: :data( 1 ) , :left ( Tree.new: data => 2 , :left ( Tree.new: data => -6 ) ) , :rite ( Tree.new: :data( 3 ) , :left ( Tree.new: data => -4 ) , :rite ( Tree.new: data => 5 ) )

Slide 46

Slide 46 text

Raku Trees Processing the tree: Supply a per-node function. transform() doesn’t know what it’s dispatching. Node-function only sees the current node.

Slide 47

Slide 47 text

Raku Trees Sum the tree: Raku treats the code fragment as a sub.. “*.data” is current object’s data value. $sum += accumulates the total. my $sum = 0; $tree.traverse( $sum += *.data ); say "sum = $sum";

Slide 48

Slide 48 text

Raku Trees Sum the tree: Raku treats the code fragment as a sub.. “*.data” is current object’s data value. $sum += accumulates the total. my $sum = 0; $tree.traverse( $sum += *.data ); say "sum = $sum";

Slide 49

Slide 49 text

Raku Trees Sum the tree: Raku treats the code fragment as a sub.. “*.data” is current object’s data value. $sum += accumulates the total. my $sum = 0; $tree.traverse( $sum += *.data ); say "sum = $sum";

Slide 50

Slide 50 text

Raku Trees Sum the tree: 0 += -6 -6 += 2 -4 += 1 -3 += -4 -7 += 3 my $sum = 0; $tree.traverse( $sum += *.data ); say "sum = $sum";

Slide 51

Slide 51 text

Raku Trees $sum is encapsulated by the closure. traverse() has no idea $sum exists. The tree class has no access to $sum. my $sum = 0; $tree.traverse( $sum += *.data ); say "sum = $sum";

Slide 52

Slide 52 text

Raku Trees Find the maximum: Explicit signature “-> $node”. Applied to anonymous “{…}”. “max=” similar to “+=”. my $largest = -∞; $tree.traverse( -> $node { $largest max= $node.value } ); say "max = $largest";

Slide 53

Slide 53 text

Raku Trees Find the maximum: Explicit signature “-> $node”. Applied to anonymous “{…}”. “max=” similar to “+=”. my $largest = -∞; $tree.traverse( -> $node { $largest max= $node.value } ); say "max = $largest";

Slide 54

Slide 54 text

Raku Trees Collect negativity: No signiture uses $_ as the value. Implicit $_ in “.data”. my @losses; $tree.traverse( { @losses.push: .data if .data < 0 } ); say "losses = @losses[]";

Slide 55

Slide 55 text

Raku Trees Collect negativity: No signiture uses $_ as the value. Implicit $_ in “.data”. my @losses; $tree.traverse( { @losses.push: .data if .data < 0 } ); say "losses = @losses[]";

Slide 56

Slide 56 text

Raku Trees Eliminate negativity: This uses the ‘whatever’ form. “*.data max= $limit” replaces negatives with zero. Works with “data is rw” in the tree class spec. my $limit = 0; $tree.traverse( *.data max= $limit );

Slide 57

Slide 57 text

Raku Trees Display the data: Anonymous code block. Explicit “$^” variable. my $i = 0; $tree.traverse( { say “{++$i} : {$^curr_node.data}” } );

Slide 58

Slide 58 text

Raku Trees Display the data: Anonymous code block. Explicit “$^” variable. my $i = 0; $tree.traverse( { say “{++$i} : {$^curr_node.data}” } );

Slide 59

Slide 59 text

Closures in Raku Are just closures. Flexible sub declaration: For multiple args use autovars: $^x, $^y. Whatever notation is forgiving of types. $_ is succinct.

Slide 60

Slide 60 text

Dispatching into subrefs Perl can dispatch via a scalar: $foo->$bar; Works with: $bar = ‘subname’; # uses inheritence $bar = sub { … }; # bypass “

Slide 61

Slide 61 text

Dispatching into subrefs In fact, any scalar works: my $message= ‘Hello, there’; my $handler= sub { … }; Dispatched as: $message->$handler; $message->$handler( $more_args, … );

Slide 62

Slide 62 text

Dispatching into subrefs This can be handy in a few places: Breaking up huge subs. Overriding behavior. Configuring behavior.

Slide 63

Slide 63 text

Dispatching into subrefs my $prefix = config( ‘test_prefix’); my $debug = $ENV{ TESTING } ? sub { say join “\n” => $prefix, @_ } : sub {} ; $message->$debug; $object->$debug( ‘Starting step 1’ );

Slide 64

Slide 64 text

Dispatching into subrefs We’ve all dealt with over-long, daisy-chain logic. Fix: Break out chunks of logic into anonymous subs. Dispatch them from the original sub. Effectively ‘private subs’. Closures allow configurable handling.

Slide 65

Slide 65 text

Closures in general Flexible runtime encapsulation. Combine with static boilerplate. Provides flexible, re-usable solutions.

Slide 66

Slide 66 text

References Last year’s closure talks: https://speakerdeck.com/lembark/locked-in-a-box- closures-in-raku https://speakerdeck.com/lembark/locked-in-a-box- closures-in-perl5