Sun Sep 10 01:38:14 UTC 2023

The Hidden Power of Perl Prototypes

--

(back post, originally posted at blogs.perl.org)

   https://blogs.perl.org/users/oodler_577/2023/08/the-hidden-power-of-prototypes.html

Introduction

I have been leaning very heavily on Perl's prototype feature for
creating new modules. The imptetus for this can traced back to the
day I looked at the source code for Try::Tiny, and realized that
it was implemented using prototypes. The main reason for using
prototypes for many new modules I've created recently is my focus
on making a thing I do repeatedly available in a more Perlish or
idiomatic way.

Required reading if you have not and are about to dive into an
article about prototypes: Far More Than Everything You've Ever
Wanted to Know about Prototypes in Perl by Tom Christiansen.

   https://www.perlmonks.org/?node_id=861966

The following article demonstrates 2 CPAN modules I have written
that focus more on Perl programmer UX and why Perl prototypes can
provide a way forward for a great many ideas that people have.
Prototypes misunderstood, yes; but more so, they are misunderestimated.
They are infact, very powerful and when used for their intended
purpose; a lot of hand wringing can be avoided during feature
discussions.  Recent Examples on CPAN

Two such examples that I've written and uploaded to CPAN (PAUSE
ID: OODLER) are:

*  Try::ALRM (try/catch/retry like semantics for alarm and ALRM handling
*  Dispatch::Fu - one HASH based dispatcher to rule them alltm

Try::ALRM

I discussed Try::ALRM in a 2022 Perl Advent article, so I won't go
into it other than to say that it's an idiomatic way to deal with
alarm in the same way as die is handled by Try::Tiny. Once I
realizeed that an ALRM signal was thrown like an exception and
handled directly by $SIG{ALRM}, it made perfect sense to have a
try kind of interface. The benefit to this kind of UX alignment is
that it makes a retry extension to the structure very natural and
easy.

   try_once {
     my $nread = sysread $socket, $buffer, $size;
   }
   finally {
     my ($attempt, $successful) = @_;
     if (not $successful) {
        ... timed out
     }
     else {
       ... didn't
     }
   } timeout => $timeout;

Dispatch::Fu

I recently uploaded a module to CPAN called Dispatch::Fu because
I was watching a lot of hand wringing (more than usual) on the p5p
mailing list abou smartmatch, given/when, and different kinds of
switch statements.

Having grappled with this problem from a different angle before,
I felt like I recognized all of these cases as more general forms
of something a lot of Perl developers learn to love, i.e., HASH
based dispatching. This can be especially powerful for converting
a long if/elsif/else chain (that is technically O(num_branches))
into a constant time O(1) HASH look up if there is a static string
suitable for use as a HASH key. For example, this if block can be
converted to a HASH dispatch table quite readily:

   if ($cgi->param("action") eq q{show_form}) {
    ...
   }
   elsif ($cgi->param("action") eq q{process_form}) {
    ...
   }
   elsif ($cgi->param("action") eq q{show_thankyou_html}) {
    ...
   }
   else {
    ...
   }

Would become:

   my $action = $cgi->param("action");
   my $actions = {
     show_form          => sub { ... },
     process_form       => sub { ... },
     show_thankyou_html => sub { ... },
     default            => sub { ... }, # the 'else' BLOCK
   }
   $action = q{default} if (not $action or not exists $actions->{$action});
   $actions->{$action}->();

The Problem with Traditional Dispatching

This works because $action is in this case (and of is in old school
CGI.pm applications), a proper string. But idiom quickly breaks
down in the following case,

   if ($cgi->param("action") eq q{foo} and $cgi->param("userid") != 0) {
    ...
   }
   elsif ($cgi->param("action") eq q{show_thankyou_html}) {
    ...
   }
   else {
    ...
   }

The Solution to Complicated Dispatching

Dispatch::Fu

was created to expose, in a _Perlish_ and structure way, a pattern
that allows the programmer to reduce an arbitrary condition into
a specific named case, then dispatch the action based on that.
Using `Dispatch::Fu`, this would turn into the following:

   use Dispatch::Fu;
   dispatch {
     my $cgi = shift;
     if ($cgi->param("action") eq q{foo} and $cgi->param("userid") != 0) {
       return "foo";
     }
     elsif ($cgi->param("action") eq q{show_thankyou_html}) {
       return "thanks";
     }
     else {
       return "default";
     }
   } $cgi,
   on foo     => sub { ... },
   on thanks  => sub { ... },
   on default => sub { ... };

Given that example, readers should be able to see the value in this
approach. The implementation of Dispatch::Fu is tiny and it is
fast. All of the complexity is also put onto the shoulders of the
programmer in how they implement the dispatch BLOCK that is used
to determine what case name to return for immediate execution.
How Does Dispatch::Fu Work?

The remainder of this article will describe how Dispatch::Fu uses
Perl prototypes to work. At the time of this writing, Dispatch::Fu
in its entirety follows,

   package Dispatch::Fu;

   use strict;
   use warnings;
   use Exporter qw/import/;

   our $VERSION   = q{0.95};
   our @EXPORT    = qw(dispatch on);
   our @EXPORT_OK = qw(dispatch on);

   sub dispatch (&@) {
       my $code_ref  = shift;    # catch sub ref that was coerced from the 'dispatch' BLOCK
       my $match_ref = shift;    # catch the input reference passed after the 'dispatch' BLOCK

      # build up dispatch table for each k/v pair preceded by 'on'
       my $dispatch = {};
       while ( my $key = shift @_ ) {
           my $HV = shift @_;
           $dispatch->{$key} = _to_sub($HV);
       }

       # call $code_ref that needs to return a valid bucket name
       my $key = $code_ref->($match_ref);

       die qq{Computed static bucket not found\n} if not $dispatch->{$key} or 'CODE' ne ref $dispatch->{$key};

       # call subroutine ref defined as the v in the k/v $dispatch->{$key} slot
       return $dispatch->{$key}->();
   }

   sub on (@) {
       return @_;
   }

   sub _to_sub (&) {
       shift;
   }
   1;

Before I begin to explain what is happening, it is important to
clearly define what a Perl prototype is not and what it is.

     Perl prototypes is not a system for creating named parameters
     Perl prototypes is not a system for describing parameter signatures
     Perl prototypes is a way to describe Perl data type coersions
     Perl prototypes is a way to achieve Perl keyword-like or built-in functions calling UX
     Perl prototypes does use declarative, templated DSL+

(+Much like (distinctly) with pack or printf)

This table from perlsub is extremely informative and pretty much
says it all, which is a lot!:

   > perldoc perlsub
   ...
   **Declared as             Called as**
   sub mylink ($$)         mylink $old, $new
   sub myvec ($$$)         myvec $var, $offset, 1
   sub myindex ($$;$)      myindex &getstring, "substr"
   sub mysyswrite ($$$;$)  mysyswrite $buf, 0, length($buf) - $off, $off
   sub myreverse (@)       myreverse $x, $y, $z
   sub myjoin ($@)         myjoin ":", $x, $y, $z
   sub mypop (\@)          mypop @array
   sub mysplice (\@$$@)    mysplice @array, 0, 2, @pushme
   sub mykeys (\[%@])      mykeys $hashref->%*
   sub myopen (*;$)        myopen HANDLE, $name
   sub mypipe (**)         mypipe READHANDLE, WRITEHANDLE
   sub mygrep (&@)         mygrep { /foo/ } $x, $y, $z
   sub myrand (;$)         myrand 42
   sub mytime ()           mytime

What this table doesn't tell you, is that you may magnify the effect
of prototypes by combining them together in creative ways. It also
fails to suggest that you may create utility methods that do thing
like convert a bare lexical block into a subroutine reference. For
example, using the method defined with a prototype above, _to_sub,
this works as expected:

   my $CODE_ref = _to_sub {
     my @ARGS = @_
       return printf qq{Hi, there: %s!!!\n}, join('and ', @ARGS);
     };
..
     $CODE_ref->(qw/Billy Jean/);

And how doe that work? Consider the suboutine, _to_sub:

   sub _to_sub (&) {
       shift;
   }

The ampersand & in _to_sub(&) is a template that tells perl to
convert any thing passed in a lexical block into a subroutine
reference. MAGIC HAPPENS. Then the updated @_ is available to shift.
For the uninitiated, the above is explicitly equivalent to:

   sub _to_sub (&) {
       my $code_ref = shift;
       return $code_ref;
   }

So what's shift'd to $code_ref (i.e., the lexical block, { ... })
has already been coerced into a CODE reference. Yes, it's MAGIC.
Another example of composing prototypes is the implementation of
the on keyword:

   sub on (@) {
       return @_;
   }

In the context of other keywords, this is a null accumulator there
merely for the UX of the whole thing. I.e., it just keeps rolling
things to its right into an array. E.g.,

   dispatch {
   ...
   } $cgi,
   on foo     => sub { ... },
   on thanks  => sub { ... },
   on default => sub { ... };

Could validly be written as,

   dispatch {
   ...
   } $cgi,
    foo     => sub { ... },
    thanks  => sub { ... },
    default => sub { ... };

If you are thinking on is just a hint for mere mortals, then you
are correct!

And now it can be pointed out that dispatch is really just set up
to take a list of things; the first thing is a CODE reference
(coerced by &), the second thing is the SCALAR reference that is
passed to $code_ref as its only parameter (see dispatch's implementation
above) that is rolled into @; then the rest of @, which contains
all the case/sub pairs:

   sub dispatch (&@) {
      my $code_ref  = shift; # captures lexical block, { ... }
      my $match_ref = shift; # captures $CASES
      ...

There rest of the call just builds up the $dispatch HASH reference,
calls $code_ref by passing in $match_ref, then assumes $code_ref
will return a key that is contained in $dispatch. Then that key is
used to call the subroutine assumed to be stored in $dispatch by
reference. Thats it!  More Hidden Features of Prototypes Remain

In all my years, I have not seen a lot written on prototypes other
than FUD. I certainly have not seen anything that aimed to expose
interesting structures or even classes of structures. I believe I
may have just scratched the surface of what is possible, and I
encourage others to explore the hidden taxonomy of structures that
seems to be lurking just below the surface.

For example, the general structure of Dispatch::Fu is covered in
the table above as mygrep { /foo/ } $x, $y, $z. The on keyword is
effectively putting syntactical sugar around the creation of the
list on the lefthand side of the mygrep BLOCK.

The other thing to not with this structure is that there is no
comma immediately after the BLOCK, but commas are required there
after. Using on, the requirement to have a successive trail of
commas (recall => is equivalent to a comma, often called the Texas
comma) is broken; the pattern is therefore:

   dispatch BLOCK REF,
   on string1 => CODE,
   on string2 => CODE,
   ...,
   on stringN => CODE;

But the accumulator, on(@) as defined effectively works to filter
out the on. I think that is an interesting effect, and the current
system can be exploited to do some very interesting things with
syntax structure. Which means it is equivalent to,

   dispatch BLOCK REF, string1, CODE, strint2, CODE, ..., stringN, CODE;

And this is where the mygrep pattern becomes apparent:

   mygrep   {...} $x,  $y,      $z, ...;

If you have noticed or done anything interesting to exploit Perl
prototype syntax in interesting or surprising ways, please let
everyone know in the comments below. Untill next time, take care!

Read it HTML formatted at:

   https://blogs.perl.org/users/oodler_577/2023/08/the-hidden-power-of-prototypes.html