X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=p5sagit%2FFunction-Parameters.git;a=blobdiff_plain;f=lib%2FFunction%2FParameters.pm;h=69078dc4f8d98c81f1af17f8161b0757826449f4;hp=e84c3def858aebb017a0ce99b57bdafde4a0d55f;hb=d4bd5b4af46daea81554f41159ba9abefcea75a1;hpb=63915d2641ec4983bb6b54e1ff7d30cd0032c40d diff --git a/lib/Function/Parameters.pm b/lib/Function/Parameters.pm index e84c3de..69078dc 100644 --- a/lib/Function/Parameters.pm +++ b/lib/Function/Parameters.pm @@ -2,14 +2,15 @@ package Function::Parameters; use v5.14.0; -use strict; use warnings; use Carp qw(confess); use XSLoader; BEGIN { - our $VERSION = '0.06'; + our $VERSION = '1.00_01'; + our $XS_VERSION = $VERSION; + $VERSION = eval $VERSION; XSLoader::load; } @@ -32,33 +33,51 @@ my %type_map = ( name => 'optional', default_arguments => 1, check_argument_count => 0, + named_parameters => 1, }, method => { name => 'optional', default_arguments => 1, check_argument_count => 0, + named_parameters => 1, attrs => ':method', shift => '$self', + invocant => 1, }, classmethod => { name => 'optional', default_arguments => 1, check_argument_count => 0, - attrs => ':method', + named_parameters => 1, + attributes => ':method', shift => '$class', + invocant => 1, }, ); +for my $k (keys %type_map) { + $type_map{$k . '_strict'} = { + %{$type_map{$k}}, + check_argument_count => 1, + }; +} sub import { my $class = shift; - @_ or @_ = { - fun => 'function', - method => 'method', - }; + if (!@_) { + @_ = { + fun => 'function', + method => 'method', + }; + } + if (@_ == 1 && $_[0] eq ':strict') { + @_ = { + fun => 'function_strict', + method => 'method_strict', + }; + } if (@_ == 1 && ref($_[0]) eq 'HASH') { - @_ = map [$_, $_[0]{$_}], keys %{$_[0]} - or return; + @_ = map [$_, $_[0]{$_}], keys %{$_[0]}; } my %spec; @@ -88,11 +107,17 @@ sub import { $clean{shift} = delete $type{shift} || ''; _assert_valid_identifier $clean{shift}, 1 if $clean{shift}; - $clean{attrs} = delete $type{attrs} || ''; + $clean{attrs} = join ' ', map delete $type{$_} || (), qw(attributes attrs); _assert_valid_attributes $clean{attrs} if $clean{attrs}; - $clean{default_arguments} = !!delete $type{default_arguments}; + $clean{default_arguments} = + exists $type{default_arguments} + ? !!delete $type{default_arguments} + : 1 + ; $clean{check_argument_count} = !!delete $type{check_argument_count}; + $clean{invocant} = !!delete $type{invocant}; + $clean{named_parameters} = !!delete $type{named_parameters}; %type and confess "Invalid keyword property: @{[keys %type]}"; @@ -109,6 +134,8 @@ sub import { ; $flags |= FLAG_DEFAULT_ARGS if $type->{default_arguments}; $flags |= FLAG_CHECK_NARGS if $type->{check_argument_count}; + $flags |= FLAG_INVOCANT if $type->{invocant}; + $flags |= FLAG_NAMED_PARAMS if $type->{named_parameters}; $^H{HINTK_FLAGS_ . $kw} = $flags; $^H{HINTK_SHIFT_ . $kw} = $type->{shift}; $^H{HINTK_ATTRS_ . $kw} = $type->{attrs}; @@ -142,13 +169,17 @@ Function::Parameters - subroutine definitions with parameter lists =head1 SYNOPSIS - use Function::Parameters; + use Function::Parameters qw(:strict); + # simple function fun foo($bar, $baz) { return $bar + $baz; } - fun mymap($fun, @args) :(&@) { + # function with prototype + fun mymap($fun, @args) + :(&@) + { my @res; for (@args) { push @res, $fun->($_); @@ -158,204 +189,418 @@ Function::Parameters - subroutine definitions with parameter lists print "$_\n" for mymap { $_ * 2 } 1 .. 4; + # method with implicit $self method set_name($name) { $self->{name} = $name; } + + # method with explicit invocant + method new($class: %init) { + return bless { %init }, $class; + } + + # function with optional parameters + fun search($haystack, $needle = qr/^(?!)/, $offset = 0) { + ... + } + + # method with named parameters + method resize(:$width, :$height) { + $self->{width} = $width; + $self->{height} = $height; + } + + $obj->resize(height => 4, width => 5); + + # function with named optional parameters + fun search($haystack, :$needle = qr/^(?!)/, :$offset = 0) { + ... + } + + my $results = search $text, offset => 200; -=cut +=head1 DESCRIPTION -=pod +This module extends Perl with keywords that let you define functions with +parameter lists. It uses Perl's L +API, so it works reliably and doesn't require a source filter. + +=head2 Basics + +The anatomy of a function (as recognized by this module): + +=over + +=item 1. - use Function::Parameters { - proc => 'function', - meth => 'method', - }; +The keyword introducing the function. + +=item 2. + +The function name (optional). + +=item 3. + +The parameter list (optional). + +=item 4. + +The prototype (optional). + +=item 5. + +The attribute list (optional). + +=item 6. + +The function body. + +=back + +Example: + + # (1) (2) (3) (4) (5) (6) + fun foo ($x, $y) :($$) :lvalue { ... } - my $f = proc ($x) { $x * 2 }; - meth get_age() { - return $self->{age}; - } + # (1) (6) + my $f = fun { ... }; -=head1 DESCRIPTION +In the following section I'm going to describe all parts in order from simplest to most complex. -This module lets you use parameter lists in your subroutines. Thanks to -L it works without source filters. +=head3 Body -WARNING: This is my first attempt at writing L and I have -almost no experience with perl's internals. So while this module might -appear to work, it could also conceivably make your programs segfault. -Consider this module alpha quality. +This is just a normal block of statements, as with L|perlsub>. No surprises here. -=head2 Basic stuff +=head3 Name -To use this new functionality, you have to use C instead of C - -C continues to work as before. The syntax is almost the same as for -C, but after the subroutine name (or directly after C if you're -writing an anonymous sub) you can write a parameter list in parentheses. This -list consists of comma-separated variables. +If present, it specifies the name of the function being defined. As with +L|perlsub>, if a name is present, the whole declaration is syntactically +a statement and its effects are performed at compile time (i.e. at runtime you +can call functions whose definitions only occur later in the file). If no name +is present, the declaration is an expression that evaluates to a reference to +the function in question. No surprises here either. -The effect of C is as if you'd written -C, i.e. the parameter list is simply -copied into C and initialized from L<@_|perlvar/"@_">. +=head3 Attributes -In addition you can use C, which understands the same syntax as C -but automatically creates a C<$self> variable for you. So by writing -C you get the same effect as -C. +Attributes are relatively unusual in Perl code, but if you want them, they work +exactly the same as with L|perlsub/Subroutine-Attributes>. -=head2 Customizing the generated keywords +=head3 Prototype -You can customize the names of the keywords injected into your scope. To do -that you pass a hash reference in the import list: +As with L|perlsub/Prototypes>, a prototype, if present, contains hints as to how +the compiler should parse calls to this function. This means prototypes have no +effect if the function call is compiled before the function declaration has +been seen by the compiler or if the function to call is only determined at +runtime (e.g. because it's called as a method or through a reference). - use Function::Parameters { proc => 'function', meth => 'method' }; # -or- - use Function::Parameters { proc => 'function' }; # -or- - use Function::Parameters { meth => 'method' }; # etc. +With L|perlsub>, a prototype comes directly after the function name (if +any). C reserves this spot for the +L. To specify a prototype, put it as the +first attribute (e.g. C). This is syntactically unambiguous +because normal L need a name after the colon. -The first line creates two keywords, C and C (for defining -functions and methods, respectively). The last two lines only create one -keyword. Generally the hash keys can be any identifiers you want while the -values have to be either C, C, C or a hash -reference (see below). The difference between C and C is that -Cs automatically L their first argument into -C<$self> (Cs are similar but shift into C<$class>). +=head3 Parameter list -The following shortcuts are available: +The parameter list is a list of variables enclosed in parentheses, except it's +actually a bit more complicated than that. A parameter list can include the +following 6 parts, all of which are optional: - use Function::Parameters; - # is equivalent to # - use Function::Parameters { fun => 'function', method => 'method' }; +=over -=cut +=item 1. Invocant -=pod +This is a scalar variable followed by a colon (C<:>) and no comma. If an +invocant is present in the parameter list, the first element of +L|perlvar/@ARG> is automatically Led|perlfunc/shift> off and +placed in this variable. This is intended for methods: + + method new($class: %init) { + return bless { %init }, $class; + } + + method throw($self:) { + die $self; + } + +=item 2. Required positional parameters + +The most common kind of parameter. This is simply a comma-separated list of +scalars, which are filled from left to right with the arguments that the caller +passed in: + + fun add($x, $y) { + return $x + $y; + } + + say add(2, 3); # "5" + +=item 3. Optional positional parameters + +Parameters can be marked as optional by putting an equals sign (C<=>) and an +expression (the "default argument") after them. If no corresponding argument is +passed in by the caller, the default argument will be used to initialize the +parameter: + + fun scale($base, $factor = 2) { + return $base * $factor; + } + + say scale(3, 5); # "15" + say scale(3); # "6" + +The default argument is I cached. Every time a function is called with +some optional arguments missing, the corresponding default arguments are +evaluated from left to right. This makes no difference for a value like C<2> +but it is important for expressions with side effects, such as reference +constructors (C<[]>, C<{}>) or function calls. + +Default arguments see not only the surrounding lexical scope of their function +but also any preceding parameters. This allows the creation of dynamic defaults +based on previous arguments: + + method set_name($self: $nick = $self->default_nick, $real_name = $nick) { + $self->{nick} = $nick; + $self->{real_name} = $real_name; + } + + $obj->set_name("simplicio"); # same as: $obj->set_name("simplicio", "simplicio"); + +Because default arguments are actually evaluated as part of the function body, +you can also do silly things like this: + + fun foo($n = return "nope") { + "you gave me $n" + } + + say foo(2 + 2); # "you gave me 4" + say foo(); # "nope" + +=item 4. Required named parameters + +By putting a colon (C<:>) in front of a parameter you can make it named +instead of positional: + + fun rectangle(:$width, :$height) { + ... + } + + rectangle(width => 2, height => 5); + rectangle(height => 5, width => 2); # same thing! + +That is, the caller must specify a key name in addition to the value, but in +exchange the order of the arguments doesn't matter anymore. As with hash +initialization, you can specify the same key multiple times and the last +occurrence wins: + + rectangle(height => 1, width => 2, height => 2, height => 5; + # same as: rectangle(width => 2, height => 5); + +You can combine positional and named parameters as long as the positional +parameters come first: + + fun named_rectangle($name, :$width, :$height) { + ... + } + + named_rectangle("Avocado", width => 0.5, height => 1.2); + +=item 5. Optional named parameters -The following shortcuts are deprecated and may be removed from a future version -of the module: +As with positional parameters, you can make named parameters optional by +specifying a default argument after an equals sign (C<=>): - # DEPRECATED - use Function::Parameters 'foo'; - # is equivalent to # - use Function::Parameters { 'foo' => 'function' }; + fun rectangle(:$width, :$height, :$color = "chartreuse") { + ... + } + + rectangle(height => 10, width => 5); + # same as: rectangle(height => 10, width => 5, color => "chartreuse"); =cut =pod + + fun get($url, :$cookie_jar = HTTP::Cookies->new(), :$referrer = $url) { + ... + } + + my $data = get "http://www.example.com/", referrer => undef; # overrides $referrer = $url + +The above example shows that passing any value (even C) will override +the default argument. + +=item 6. Slurpy parameter + +Finally you can put an array or hash in the parameter list, which will gobble +up the remaining arguments (if any): + + fun foo($x, $y, @rest) { ... } + + foo "a", "b"; # $x = "a", $y = "b", @rest = () + foo "a", "b", "c"; # $x = "a", $y = "b", @rest = ("c") + foo "a", "b", "c", "d"; # $x = "a", $y = "b", @rest = ("c", "d") + +If you combine this with named parameters, the slurpy parameter will end up +containing all unrecognized keys: + + fun bar(:$size, @whatev) { ... } + + bar weight => 20, size => 2, location => [0, -3]; + # $size = 2, @whatev = ('weight', 20, 'location', [0, -3]) + +=back + +Apart from the L|perlfunc/shift> performed by the L, all of the above leave L|perlvar/@ARG> unchanged; and if you +don't specify a parameter list at all, L|perlvar/@ARG> is all you get. + +=head3 Keyword - # DEPRECATED - use Function::Parameters 'foo', 'bar'; - # is equivalent to # - use Function::Parameters { 'foo' => 'function', 'bar' => 'method' }; +The keywords provided by C are customizable. Since +C is actually a L, the provided +keywords have lexical scope. The following import variants can be used: -That is, if you want to pass arguments to L, use a -hashref, not a list of strings. +=over + +=item C + +Provides the keywords C and C (described below) and enables +argument checks so that calling a function and omitting a required argument (or +passing too many arguments) will throw an error. + +=item C -You can customize things even more by passing a hashref instead of C -or C. This hash can have the following keys: +Provides the keywords C and C (described below) and enables +"lax" mode: Omitting a required argument sets it to C while excess +arguments are silently ignored. + +=item C<< use Function::Parameters { KEYWORD1 => TYPE1, KEYWORD2 => TYPE2, ... } >> + +Provides completely custom keywords as described by their types. A "type" is +either a string (one of the predefined types C, C, +C, C, C, C) or +a reference to a hash with the following keys: =over =item C -Valid values: C (default), C (all uses of this keyword must -specify a function name), and C (all uses of this keyword must not -specify a function name). This means a C<< name => 'prohibited' >> keyword can -only be used for defining anonymous functions. +Valid values: C (default), C (all functions defined with +this keyword must have a name), and C (functions defined with this +keyword must be anonymous). =item C -Valid values: strings that look like a scalar variable. Any function created by -this keyword will automatically L its first argument into -a local variable whose name is specified here. +Valid values: strings that look like scalar variables. This lets you specify a +default L, i.e. a function defined with this keyword +that doesn't have an explicit invocant in its parameter list will automatically +L|perlfunc/shift> its first argument into the variable specified here. -=item C +=item C -Valid values: strings that are valid source code for attributes. Any value -specified here will be inserted as a subroutine attribute in the generated -code. Thus: +Valid values: booleans. If you set this to a true value, the keyword will +accept L in parameter lists; otherwise specifying +an invocant in a function defined with this keyword is a syntax error. - use Function::Parameters { sub_l => { attrs => ':lvalue' } }; - sub_l foo() { - ... - } +=item C -turns into +Valid values: strings containing (source code for) attributes. This causes any +function defined with this keyword to have the specified +L (in addition to any attributes specified in the +function definition itself). - sub foo :lvalue { - ... - } +=item C + +Valid values: booleans. This property is on by default; use +C<< default_arguments => 0 >> to turn it off. This controls whether optional +parameters are allowed. If it is turned off, using C<=> in parameter lists is +a syntax error. + +=item C + +Valid values: booleans. If turned on, functions defined with this keyword will +automatically check that they have been passed all required arguments and no +excess arguments. If this check fails, an exception will by thrown via +L|Carp>. =back -Plain C<'function'> is equivalent to C<< { name => 'optional' } >>, plain -C<'method'> is equivalent to -C<< { name => 'optional', shift => '$self', attrs => ':method' } >>, and plain -C<'classmethod'> is equivalent to -C<< { name => 'optional', shift => '$class', attrs => ':method' } >>. - -=head2 Syntax and generated code - -Normally, Perl subroutines are not in scope in their own body, meaning the -parser doesn't know the name C or its prototype while processing the body -of C, parsing it as -C<$bar-Efoo([1], $bar[0])>. Yes. You can add parens to change the -interpretation of this code, but C will only trigger -a I warning. This module attempts -to fix all of this by adding a subroutine declaration before the definition, -so the parser knows the name (and possibly prototype) while it processes the -body. Thus C really turns into -C. - -If you need L, you can -put them after the parameter list with their usual syntax. - -Syntactically, these new parameter lists live in the spot normally occupied -by L. However, you can include a prototype by -specifying it as the first attribute (this is syntactically unambiguous -because normal attributes have to start with a letter while a prototype starts -with C<(>). - -As an example, the following declaration uses every feature available -(subroutine name, parameter list, prototype, attributes, and implicit -C<$self>): - - method foo($x, $y, @z) :($;$@) :lvalue :Banana(2 + 2) { - ... +The predefined type C is equivalent to: + + { + name => 'optional', + invocant => 0, + default_arguments => 1, + check_argument_count => 0, } -And here's what it turns into: +These are all default values, so C is also equivalent to C<{}>. - sub foo ($;$@); sub foo ($;$@) :lvalue :Banana(2 + 2) { my $self = shift; my ($x, $y, @z) = @_; - ... +C is equivalent to: + + { + name => 'optional', + shift => '$self', + invocant => 1, + attributes => ':method', + default_arguments => 1, + check_argument_count => 0, } -Another example: - my $coderef = fun ($p, $q) :(;$$) - :lvalue - :Gazebo((>:O)) { - ... - }; +C is equivalent to: -And the generated code: + { + name => 'optional', + shift => '$class', + invocant => 1, + attributes => ':method', + default_arguments => 1, + check_argument_count => 0, + } - my $coderef = sub (;$$) :lvalue :Gazebo((>:O)) { my ($p, $q) = @_; - ... - }; +C, C, and +C are like C, C, and +C, respectively, but with C<< check_argument_count => 1 >>. + +=back + +Plain C is equivalent to +C<< use Function::Parameters { fun => 'function', method => 'method' } >>. -=head2 Wrapping Function::Parameters +C is equivalent to +C<< use Function::Parameters { fun => 'function_strict', method => 'method_strict' } >>. -If you want to wrap L, you just have to call its -C method. It always applies to the file that is currently being parsed -and its effects are lexical (i.e. it works like L or L): +=head2 Wrapping C + +If you want to write a wrapper around C, you only have to +call its C method. Due to its L nature it always +affects the file that is currently being compiled. package Some::Wrapper; use Function::Parameters (); sub import { Function::Parameters->import; - # or Function::Parameters->import(@other_import_args); + # or Function::Parameters->import(@custom_import_args); } +=head2 How it works + +The module is actually written in L and uses +L|perlapi/PL_keyword_plugin> to generate opcodes directly. +However, you can run L|B::Deparse> on your code to see +what happens under the hood. In the simplest case (no argument checks, possibly +an L, required positional/slurpy parameters only), the +generated code corresponds to: + + fun foo($x, $y, @z) { ... } + # ... turns into ... + sub foo { my ($x, $y, @z) = @_; sub foo; ... } + + method bar($x, $y, @z) { ... } + # ... turns into ... + sub bar :method { my $self = shift; my ($x, $y, @z) = @_; sub bar; ... } + =head1 AUTHOR Lukas Mai, C<< >>