Tag: signatures

  • De-​experimentalizing Perl subroutine signatures

    On the heels of my blog arti­cle and upcom­ing pre­sen­ta­tion comes Paul Evans’ call to de-​experimentalize (is that a word?) sub­rou­tine sig­na­tures in Perl core. It’s been sta­ble for over four years now, and the exper­i­men­tal” tag has been hold­ing back devel­op­ers big and small, so I ful­ly sup­port this effort. Maybe it can make it into Perl 5.34? Here’s hoping.

  • I’m presenting at Houston Perl Mongers this week

    I’ll be pre­sent­ing Better Perl: Subroutine Signatures and Type Validation” (based on my ear­li­er blog post) for Houston Perl Mongers on Thursday, February 11 at 6:00 PM Central US time.

    Check here to see what time that is in your local time zone, then go here at that time to open the Zoom meet­ing ID 920 069 702. You’ll need a pass­word; run this Perl code to get that:

    print +(0b1000100).((3<<2)*10).(010)
  • Better Perl with subroutine signatures and type validation

    Better Perl with subroutine signatures and type validation

    Did you know that you could increase the read­abil­i­ty and reli­a­bil­i­ty of your Perl code with one fea­ture? I’m talk­ing about sub­rou­tine sig­na­tures: the abil­i­ty to declare what argu­ments, and in some cas­es what types of argu­ments, your func­tions and meth­ods take.

    Most Perl pro­gram­mers know about the @_ vari­able (or @ARG if you use English). When a sub­rou­tine is called, @_ con­tains the para­me­ters passed. It’s an array (thus the @ sig­il) and can be treat­ed as such; it’s even the default argu­ment for pop and shift. Here’s an example:

    use v5.10;
    use strict;
    use warnings;
    
    sub foo {
        my $parameter = shift;
        say "You passed me $parameter";
    }
    

    Or for mul­ti­ple parameters:

    use v5.10;
    use strict;
    use warnings;
    
    sub foo {
        my ($parameter1, $parameter2) = @_;
        say "You passed me $parameter1 and $parameter2";
    }
    

    (What’s that use v5.10; doing there? It enables all fea­tures that were intro­duced in Perl 5.10, such as the say func­tion. We’ll assume you type it in from now on to reduce clutter.)

    We can do bet­ter, though. Perl 5.20 (released in 2014; why haven’t you upgrad­ed?) intro­duced the exper­i­men­tal signatures fea­ture, which as described above, allows para­me­ters to be intro­duced right when you declare the sub­rou­tine. It looks like this:

    use experimental 'signatures';
    
    sub foo ($parameter1, $parameter2 = 1, @rest) {
        say "You passed me $parameter1 and $parameter2";
        say "And these:";
        say for @rest;
    }
    

    You can even set defaults for option­al para­me­ters, as seen above with the = sign, or slurp up remain­ing para­me­ters into an array, like the @rest array above. For more help­ful uses of this fea­ture, con­sult the perl­sub man­u­al page.

    We can do bet­ter still. The Comprehensive Perl Archive Network (CPAN) con­tains sev­er­al mod­ules that both enable sig­na­tures, as well as val­i­date para­me­ters are of a cer­tain type or for­mat. (Yes, Perl can have types!) Let’s take a tour of some of them.

    Params::Validate

    This mod­ule adds two new func­tions, validate() and validate_pos(). validate() intro­duces named para­me­ters, which make your code more read­able by describ­ing what para­me­ters are being called at the time you call them. It looks like this:

    use Params::Validate;
    
    say foo(parameter1 => 'hello',  parameter2 => 'world');
    
    sub foo {
        my %p = validate(@_, {
            parameter1 => 1, # mandatory
            parameter2 => 0, # optional
        } );
        return $p->{parameter1}, $p->{parameter2};
    }
    

    If all you want to do is val­i­date un-​named (posi­tion­al) para­me­ters, use validate_pos():

    use Params::Validate;
    
    say foo('hello', 'world');
    
    sub foo {
        my @p = validate_pos(@_, 1, 0);
        return @p;
    }
    

    Params::Validate also has fair­ly deep sup­port for type val­i­da­tion, enabling you to val­i­date para­me­ters against sim­ple types, method inter­faces (also known as duck typ­ing”), mem­ber­ship in a class, reg­u­lar expres­sion match­es, and arbi­trary code call­backs. As always, con­sult the doc­u­men­ta­tion for the nitty-​gritty details.

    MooseX::Params::Validate

    MooseX::Params::Validate adds type val­i­da­tion via the Moose object-​oriented frame­work’s type sys­tem, mean­ing that any­thing that can be defined as a Moose type can be used to val­i­date the para­me­ters passed to your func­tions or meth­ods. It adds the validated_hash(), validated_list(), and pos_validated_list() func­tions, and looks like this:

    package Foo;
    
    use Moose;
    use MooseX::Params::Validate;
    
    say __PACKAGE__->foo(parameter1 => 'Mouse');
    say __PACKAGE__->bar(parameter1 => 'Mice');
    say __PACKAGE__->baz('Men', 42);
    
    sub foo {
        my ($self, %params) = validated_hash(
            \@_,
            parameter1 => { isa => 'Str', default => 'Moose' },
        );
        return $params{parameter1};
    }
    
    sub bar {
        my ($self, $param1) = validated_pos(
            \@_,
            parameter1 => { isa => 'Str', default => 'Moose' },
        );
        return $param1;
    }
    
    sub baz {
        my ($self, $foo, $bar) = pos_validated_list(
            \@_,
            { isa => 'Str' },
            { isa => 'Int' },
        );
        return $foo, $bar;
    }
    

    Note that the first para­me­ter passed to each func­tion is a ref­er­ence to the @_ array, denot­ed by a backslash.

    MooseX::Params::Validate has sev­er­al more things you can spec­i­fy when list­ing para­me­ters, includ­ing roles, coer­cions, and depen­den­cies. The doc­u­men­ta­tion for the mod­ule has all the details. We use this mod­ule at work a lot, and even use it with­out Moose when val­i­dat­ing para­me­ters passed to test functions.

    Function::Parameters

    For a dif­fer­ent take on sub­rou­tine sig­na­tures, you can use the Function::Parameters mod­ule. Rather than pro­vid­ing helper func­tions, it defines two new Perl key­words, fun and method. It looks like this:

    use Function::Parameters;
    
    say foo('hello', 'world');
    say bar(param1 => 'hello');
    
    fun foo($param1, $param2) {
        return $param1, $param2;
    }
    
    fun bar(:$param1, :$param2 = 42) {
        return $param1, $param2;
    }
    

    The colons in the bar() func­tion above indi­cate that the para­me­ters are named, and need to be spec­i­fied by name when the func­tion is called, using the => oper­a­tor as if you were spec­i­fy­ing a hash.

    In addi­tion to defaults and the posi­tion­al and named para­me­ters demon­strat­ed above, Function::Parameters sup­ports type con­straints (via Type::Tiny) and Moo or Moose method mod­i­fiers. (If you don’t know what those are, the Moose and Class::Method::Modifiers doc­u­men­ta­tion are helpful.)

    I’m not a fan of mod­ules that add new syn­tax for com­mon tasks like sub­rou­tines and meth­ods, if only because there’s an extra effort in updat­ing tool­ings like syn­tax high­lighters and Perl::Critic code analy­sis. Still, this may appeal to you, espe­cial­ly if you’re com­ing from oth­er lan­guages that have sim­i­lar syntax.

    Type::Params

    Speaking of Type::Tiny, it includes its own para­me­ter val­i­da­tion library called Type::Params. I think I would favor this for new work, as it’s com­pat­i­ble with both Moo and Moose but does­n’t require them.

    Type::Params has a num­ber of func­tions, none of which are pro­vid­ed by default, so you’ll have to import them explic­it­ly when use ing the mod­ule. It also intro­duces a sep­a­rate step for com­pil­ing your val­i­da­tion spec­i­fi­ca­tion to speed up per­for­mance. It looks like this:

    use Types::Standard qw(Str Int);
    use Type::Params qw(compile compile_named);
    
    say foo('hello', 42);
    say bar(param1 => 'hello');
    
    sub foo {
        state $check = compile(Str, Int);
        my ($param1, $param2) = $check->(@_);
    
        return $param1, $param2;
    }
    
    sub bar {
        state $check = compile_named(
            param1 => Str,
            param2 => Int, {optional => 1},
        );
        my $params_ref = $check->(@_);
    
        return $params_ref->{param1}, $params_ref->{param2};
    }
    

    The fea­tures of Type::Tiny and its bun­dled mod­ules are pret­ty vast, so I sug­gest once again that you con­sult the doc­u­men­ta­tion on how to use it.

    Params::ValidationCompiler

    At the top of the doc­u­men­ta­tion to Params::Validate, you’ll notice that the author rec­om­mends instead his Params::ValidationCompiler mod­ule for faster per­for­mance, using a com­pi­la­tion step much like Type::Params. It pro­vides two func­tions for you to import, validation_for() and source_for(). We’ll con­cen­trate on the for­mer since the lat­ter is main­ly use­ful for debugging.

    It looks like this:

    use Types::Standard qw(Int Str);
    use Params::ValidationCompiler 'validation_for';
    
    my $validator = validation_for(
        params => {
            param1 => {
                type    => Str,
                default => 'Perl is cool',
            },
            param2 => {
                type     => Int,
                optional => 1,
            },
    );
    
    say foo(param1 => 'hello');
    
    sub foo {
        my %params = $validator->(@_);
        return @params{'param1', 'param2'};
    }
    

    As you can see, it sup­ports type con­straints, defaults, and option­al val­ues. It can also put extra argu­ments in a list (it calls this fea­ture slur­py”), and can even return gen­er­at­ed objects to make it eas­i­er to catch typos (since a typoed hash key just gen­er­ates that key rather than return­ing an error). There’s a bit more to this mod­ule, so please read the doc­u­men­ta­tion to exam­ine all its features.

    Conclusion

    One of Perl’s mot­tos is there’s more than one way to do it,” and you’re wel­come to choose what­ev­er method you need to enable sig­na­tures and type val­i­da­tion. Just remem­ber to be con­sis­tent and have good rea­sons for your choic­es, since the over­all goal is to improve your code’s reli­a­bil­i­ty and read­abil­i­ty. And be sure to share your favorite tech­niques with oth­ers, so they too can devel­op bet­ter software.

    This arti­cle is also pub­lished on dev.to.