Upgrade to Pro — share decks privately, control downloads, hide ads and more …

More closures in Perl & Raku

More closures in Perl & Raku

Closures are immensely simple, powerful, flexible constructs that simplify a variety of situations programming. Perl and Raku have "first-class" sub's and thus offer effective closures. This talk extends last year's descriptions of using closures to a Flyweight object in Perl ("Object::Trampoline") and a basic Raku Tree class, showing ways to integrate Raku's coderef syntax with the class.

Steven Lembark

June 27, 2024
Tweet

More Decks by Steven Lembark

Other Decks in Technology

Transcript

  1. Closures are simple enough A piece of code ‘closes over’

    some local variable(s). They require first-class subs (a’la LISP)
  2. 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).
  3. 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.
  4. 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.
  5. 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.
  6. 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 );
  7. 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 );
  8. 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 );
  9. Using a flyweight Construct the flyweight. my $dict = Object::Trampoline->create

    ( 'Lang::Translate', 'Trad Manderin' ); # dict is O::T
  10. 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”
  11. 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 }
  12. 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 }
  13. 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 }
  14. 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{}
  15. 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{}
  16. 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{}
  17. 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{}
  18. 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{}
  19. 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{}
  20. 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{}
  21. 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{}
  22. 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.
  23. 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.
  24. 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
  25. 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) {...}
  26. 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) {...}
  27. 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) {...}
  28. 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) {...}
  29. 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) {...}
  30. 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) {...}
  31. 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) {...}
  32. 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; } }
  33. Raku Trees Tree Itself: data and child branches. class Tree

    { has $.data is rw; has $.left; has $.rite; }
  34. Raku Trees Method has implicit object argument. class Tree {

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

    Tree { method traverse ( &handler ) { .traverse( &handler ) with $.left; handler( self ); .traverse( &handler ) with $.rite; } }
  36. 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; } }
  37. Raku Trees Requires a code object via “&” in argument.

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

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

    method traverse ( &handler ) { .traverse( &handler ) with $.left; handler( self ); .traverse( &handler ) with $.rite; } }
  40. 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 ) )
  41. 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 ) )
  42. 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 ) )
  43. 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 ) )
  44. 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.
  45. 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";
  46. 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";
  47. 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";
  48. 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";
  49. 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";
  50. 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";
  51. 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";
  52. 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[]";
  53. 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[]";
  54. 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 );
  55. Raku Trees Display the data: Anonymous code block. Explicit “$^”

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

    variable. my $i = 0; $tree.traverse( { say “{++$i} : {$^curr_node.data}” } );
  57. Closures in Raku Are just closures. Flexible sub declaration: For

    multiple args use autovars: $^x, $^y. Whatever notation is forgiving of types. $_ is succinct.
  58. Dispatching into subrefs Perl can dispatch via a scalar: $foo->$bar;

    Works with: $bar = ‘subname’; # uses inheritence $bar = sub { … }; # bypass “
  59. Dispatching into subrefs In fact, any scalar works: my $message=

    ‘Hello, there’; my $handler= sub { … }; Dispatched as: $message->$handler; $message->$handler( $more_args, … );
  60. Dispatching into subrefs This can be handy in a few

    places: Breaking up huge subs. Overriding behavior. Configuring behavior.
  61. 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’ );
  62. 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.