The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Sub::Declaration - declare subs with named parameters

SYNOPSIS

        use Sub::Declaration;

        sub mysub($scalar, \@array,) {
            $array[$scalar] += $_ foreach (@_);
        }

        method mymethod($scalar, %hash) {
            print $hash{$scalar}, "\n";
        }

DESCRIPTION

Sub::Declaration has three purposes:

  • Allowing subroutines to be declared and defined with a named list of arguments, as in most familiar programming languages.

  • Creating aliases of these parameters to the appropriate values.

  • Allowing methods to be created with automatic declaration of the variable containing the object reference.

For example, the "standard" way to write the example in the "SYNOPSIS" is something like

        sub mysub ($\@@) {
            my ($scalar, $arrayref) = splice @_, 0, 2;
            $arrayref->[$scalar] += $_ foreach (@_);
        }

The variable $arrayref is simply a reference to an array, and you must remember to appropriately dereference it where necessary. By creating a variable named @array that is aliased to the equivalent of @$arrayref, you can avoid the need to dereference (Perl6::Binding is a way to create lexical aliases explicitly):

        use Perl6::Binding;

        sub mysub ($\@@) {
            my ($scalar, $arrayref) = splice @_, 0, 2;
            my @array := @$arrayref;

            $array[$scalar] += $_ foreach (@_);
        }

This module combines the extraction from the argument array and the creation of the alias into one operation.

Why Another Module?

There are already several modules for handling named subroutine parameters, such as Perl6::Parameters, Sub::Parameters, and Sub::NamedParams. So why another one?

The following reasons:

  • I wanted to implement the automatic aliasing of the parameters to lexical variables, in order to allow Perl's standard pass-by-reference to be used when necessary. In particular, I wanted to be able to specify that an array or hash reference is expected, and set things up so it is not necessary to dereference each use of the array or hash.

  • I wanted an unobtrusive syntax for declaring the parameters. If this were all I wanted I'd just have used Perl6::Parameters. The syntax for specifying parameters in the other modules just struck me as rather clunky.

  • I wanted to be able to declare methods and have the "$self" variable automatically created and populated.

DECLARATIONS

All declarations use the items specified in "ARGUMENT LISTS" below.

sub

sub name(argument list) { ... }

The argument list is just a list of variable names, separated by commas. When the subroutine is called, these variables are associated with the items in @_.

method

method name(argument list) { ... }

This is useful to declare the methods in a class. It is identical to the "sub" declaration, but inserts a variable named $self into the front of the parameter list, which is set to the object reference at runtime.

Thus, the declaration

        method something(@parameters) { ... }

is expanded into

        sub something($self, @parameters) : method { ... }

and then the argument list is processed.

No prototype is generated for a method declaration, since prototypes aren't honored for method calls anyway.

argument list

argument list(argument list);

Sometimes the first few arguments to a function give the context to decode the rest of the arguments. For example, consider the following skeleton:

        sub progress {
            my $type = shift;

            if ($type eq 'message') {
                my ($text) = @_;
                ...
            } elsif ($type eq 'progress') {
                my ($total, $completed) = @_;
                ...
            }
        }

This defines a subroutine that is called either as progress('message', $text) or progress('progress', $total, $completed). The way to do this using Sub::Declaration is like this:

        sub progress($type,) {
            if ($type eq 'message') {
                argument list ($text);
                ...
            } elsif ($type eq 'progress') {
                argument list ($total, $completed);
            }
        }

The argument list directive works at both compile time and runtime. At compile time it sets up the appropriate lexical variables. At runtime it populates them from @_ just as it would as if they had been named in the original argument list. Any arguments not used are left in @_ as well, so there can be multiple rounds of argument decoding; you can even do something like this:

        sub pairs($function,) {
            ## do something with $function
            while (@_) {
                argument list($name, $value);
                ## do something with $name and $value
            }
        }

While in this case it probably doesn't buy you anything over my ($name, $value) = splice @_, 0, 2, there may be cases where the ability to modify the original value in place or create aliases to referenced arrays or hashes is important.

You can use the argument list directive even if you don't supply any named parameters for a subroutine.

ARGUMENT LISTS

An argument list is just a list of variable names, separated by commas. A semicolon may appear in the list instead of one of the commas. This has no effect on the subroutine's code, but affects the generated prototype. Everything after the semicolon is optional.

There is some additional syntax that can be used with variable names, as noted in this list. $n is the index with @_ for the named parameter. This table summarizes the various forms, which are explained in detail below.

        Specification  Variable  Mode    Source           Prototype
        -------------  --------  -----   ---------------  ---------
             $name      $name    alias   $_[n]                $
        COPY $name      $name    copy    $_[n]                $
            \$name      $name    alias   $$_[n]              \$

             @name      @name    slurp   @{$_[n .. $#_]}      @
        COPY @name      @name    copy    @{$_[n]}            \@
            \@name      @name    alias   @{$_[n]}            \@

             %name      %name    slurp   @{$_[n .. $#_]}      %
        COPY %name      %name    copy    %{$_[n]}            \%
            \%name      %name    alias   %{$_[n]}            \%

             &name      $name    copy    $_[n]                &
            \&name      $name    copy    $_[n]               \&

             *name      $name    copy    $_[n]                *
            \*name      $name    copy    $_[n]               \*
$name

Unadorned scalars represent an alias to the corresponding in @_, which in turn is an alias back to the passed value. Modifying such a scalar variable causes the original passed value to be modified, or an error to be generated if the value isn't modifiable.

Generates $name aliased to $_[$n], and a prototype of $.

COPY $name

A scalar preceded by COPY indicates that a copy of the scalar is to be used rather than an alias. You cannot change the original value through this parameter.

Generates $name initialized with $_[$n], and a prototype of $.

\$name

A reference to a scalar requires $_[$n] to be a reference to a scalar. The corresponding actual parameter must begin with $, and a reference to it is passed.

Generates $name aliased to $$_[$n], and a prototype of \$.

@name
%name

Unadorned arrays or hashes slurp up the rest of the argument list.

Generates @name or %name initialized with @_[$n .. $#_], and a prototype of @ or %.

\@name
\%name

Preceding an array or hash with a backslash indicates that the single corresponding value must be a reference of the appropriate type.

Generates @name or %name aliased to @{$_[$n]} or %{$_[$n]}, and a prototype of \@ or \%.

COPY @name
COPY %name

Preceding an array or hash with COPY creates a new lexical array or hash which is filled with a (shallow) copy of the referenced array or hash. This can simplify the expression of some algorithms, but you should avoid it with large arrays or hashes.

Generates @name or @name initialized with the contents of @{$_[$n]} or %{$_[$n]}, and a prototype of \@ or \%.

&name

Indicates that the corresponding value must be a code reference. If this is the first argument, the code reference doesn't need a leading sub or trailing comma.

Since Perl does not have lexical subroutines, generates $name. You will have to explicitly dereference this. The associated prototype is &.

\&name

Almost the same as above, but the prototype is \&, which requires a subroutine name beginning with & in this slot.

*name

Indicates that the corresponding value must be a bareword, constant, scalar expression, typeglob, or a reference to a typeglob. Since there are no lexical typeglobs, generates $name containing a reference to a typeglob. The prototype is *.

\*name

Requires a reference to a typeglob. Generates $name (containing a typeglob reference) and a prototype of \*.

A trailing comma at the end of the argument list causes the generated prototype to have an extra @ at the end, but otherwise has no effect. Use this if you have a few fixed parameters followed by a variable number of items, and wish to leave the variable items in @_ rather than copying them into another array or hash.

OPTIONS

The following options may be specified when use'ing Sub::Declaration. You can change their values at any point in the program by re-use'ing the module with the new values of any options you want to change. (You will probably need to turn it off with no Sub::Declaration; first, though.)

-noproto

Turns off prototype generation.

-proto

Turns on prototype generation. This is the default unless you've changed it.

-nomethod

Turns off the generation of the method attribute for method declarations.

-method

Turns on the generation of the method attribute for method declarations. This is the default unless you've changed it.

-nodebug

Turns off debugging output. This is the default unless you've previously turned it on.

-debug

Turns on debugging output, which causes the generated code to be output to standard output as it is filtered.

-self:name

Specifies that the object reference for methods should be set to $name rather than to $self.

NOTES

  • As currently implemented, this module will expand subroutine declarations wherever they occur, even in strings or comments. You can prefix sub, method, or argument list with a backslash to turn off the expansion for that particular instance; the backslash is removed in the process of filtering the source code.

IMPLEMENTATION DETAILS

This module is implemented as a source filter (see Filter::Simple). It works by looking at all sub declarations with something that resembles an argument list as described above, and outputting the appropriate code.

For example, the following fragment from the "SYNOPSIS":

        sub mysub($scalar, \@array,) {
            $array[$scalar] += $_ foreach (@_);
        }

actually gets rewritten as something similar to

        sub mysub($\@@) {
            my ($scalar, @array);
            push @_, 1; &Sub::Declaration::arglist;
            $array[$scalar] += $_ foreach (@_);
        }

The 1 in the above example could be any integer; each argument list directive in the program is assigned a unique integer ID, which is used instead of generating instructions to pass the entire argument list definition each time.

The arglist Routine

Part of the generated code for a subroutine with named parameters calls the arglist subroutine within this module. This must be called using the &-form of subroutine call with no arguments and no trailing parentheses, after pushing a reference to an array containing the names of the variables in the argument list. There is almost certainly no reason whatsoever for you to have to call this routine explicitly, so it is not documented further.

MODULES USED

Filter::Simple, Lexical::Util (version 0.8 or later)

SEE ALSO

For other approaches, see:

Perl6::Parameters, Sub::Parameters, Sub::NamedParams

COPYRIGHT AND LICENSE

Copyright 2004 Kevin Michael Vail

This program is free software. It may be copied and/or redistributed under the same terms as Perl itself.

AUTHOR

Kevin Michael Vail <kvail@cpan.org>