X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FFunction%2FParameters.pm;h=a39f93bbbf273190ef46388f4c29f59062e13258;hb=42e595b0d7841bfda100f6af7115ea41cc46b818;hp=b0d2b2b566880cc0ce0ecaaea952a6777ec8cf9c;hpb=698e861c059c83b38a1678b02a32ff40386cac58;p=p5sagit%2FFunction-Parameters.git diff --git a/lib/Function/Parameters.pm b/lib/Function/Parameters.pm index b0d2b2b..a39f93b 100644 --- a/lib/Function/Parameters.pm +++ b/lib/Function/Parameters.pm @@ -1,15 +1,13 @@ 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.0202'; XSLoader::load; } @@ -22,43 +20,94 @@ sub _assert_valid_identifier { sub _assert_valid_attributes { my ($attrs) = @_; - $attrs =~ /^\s*:\s*[^\W\d]\w*\s*(?:(?:\s|:\s*)[^\W\d]\w*\s*)*(?:\(|\z)/ - or confess qq{"$attrs" doesn't look like valid attributes}; + $attrs =~ m{ + ^ \s*+ + : \s*+ + (?&ident) (?! [^\s:(] ) (?¶m)?+ \s*+ + (?: + (?: : \s*+ )? + (?&ident) (?! [^\s:(] ) (?¶m)?+ \s*+ + )*+ + \z + + (?(DEFINE) + (? + [^\W\d] + \w*+ + ) + (? + \( + [^()\\]*+ + (?: + (?: + \\ . + | + (?¶m) + ) + [^()\\]*+ + )*+ + \) + ) + ) + }sx or confess qq{"$attrs" doesn't look like valid attributes}; +} + +sub _reify_type_default { + require Moose::Util::TypeConstraints; + Moose::Util::TypeConstraints::find_or_create_isa_type_constraint($_[0]) +} + +sub _delete_default { + my ($href, $key, $default) = @_; + exists $href->{$key} ? delete $href->{$key} : $default } my @bare_arms = qw(function method); my %type_map = ( - function => { - name => 'optional', - default_arguments => 1, - check_argument_count => 0, - }, - method => { - name => 'optional', - default_arguments => 1, - check_argument_count => 0, - attrs => ':method', - shift => '$self', + function => {}, # all default settings + function_strict => { + defaults => 'function', + strict => 1, }, - classmethod => { - name => 'optional', - default_arguments => 1, - check_argument_count => 0, + method => { + defaults => 'function', attributes => ':method', - shift => '$class', + shift => '$self', + invocant => 1, + }, + method_strict => { + defaults => 'method', + strict => 1, + }, + classmethod => { + defaults => 'method', + shift => '$class', + }, + classmethod_strict => { + defaults => 'classmethod', + strict => 1, }, ); +our @type_reifiers = \&_reify_type_default; + 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; @@ -72,31 +121,54 @@ sub import { my ($name, $proto_type) = @$item; _assert_valid_identifier $name; - unless (ref $proto_type) { - # use '||' instead of 'or' to preserve $proto_type in the error message - $proto_type = $type_map{$proto_type} - || confess qq["$proto_type" doesn't look like a valid type (one of ${\join ', ', sort keys %type_map})]; - } + $proto_type = {defaults => $proto_type} unless ref $proto_type; my %type = %$proto_type; + while (my $defaults = delete $type{defaults}) { + my $base = $type_map{$defaults} + or confess qq["$defaults" doesn't look like a valid type (one of ${\join ', ', sort keys %type_map})]; + %type = (%$base, %type); + } + my %clean; - $clean{name} = delete $type{name} || 'optional'; + $clean{name} = delete $type{name} // 'optional'; $clean{name} =~ /^(?:optional|required|prohibited)\z/ or confess qq["$clean{name}" doesn't look like a valid name attribute (one of optional, required, prohibited)]; - $clean{shift} = delete $type{shift} || ''; + $clean{shift} = delete $type{shift} // ''; _assert_valid_identifier $clean{shift}, 1 if $clean{shift}; - $clean{attrs} = join ' ', map delete $type{$_} || (), qw(attributes attrs); + $clean{attrs} = join ' ', map delete $type{$_} // (), qw(attributes attrs); _assert_valid_attributes $clean{attrs} if $clean{attrs}; - $clean{default_arguments} = - exists $type{default_arguments} - ? !!delete $type{default_arguments} - : 1 - ; - $clean{check_argument_count} = !!delete $type{check_argument_count}; + $clean{default_arguments} = _delete_default \%type, 'default_arguments', 1; + $clean{named_parameters} = _delete_default \%type, 'named_parameters', 1; + $clean{types} = _delete_default \%type, 'types', 1; + + $clean{invocant} = _delete_default \%type, 'invocant', 0; + $clean{runtime} = _delete_default \%type, 'runtime', 0; + $clean{check_argument_count} = _delete_default \%type, 'check_argument_count', 0; + $clean{check_argument_types} = _delete_default \%type, 'check_argument_types', 0; + $clean{check_argument_count} = $clean{check_argument_types} = 1 if delete $type{strict}; + + if (my $rt = delete $type{reify_type}) { + ref $rt eq 'CODE' or confess qq{"$rt" doesn't look like a type reifier}; + + my $index; + for my $i (0 .. $#type_reifiers) { + if ($type_reifiers[$i] == $rt) { + $index = $i; + last; + } + } + unless (defined $index) { + $index = @type_reifiers; + push @type_reifiers, $rt; + } + + $clean{reify_type} = $index; + } %type and confess "Invalid keyword property: @{[keys %type]}"; @@ -107,15 +179,21 @@ sub import { my $type = $spec{$kw}; my $flags = - $type->{name} eq 'prohibited' ? FLAG_ANON_OK : - $type->{name} eq 'required' ? FLAG_NAME_OK : - FLAG_ANON_OK | FLAG_NAME_OK + $type->{name} eq 'prohibited' ? FLAG_ANON_OK : + $type->{name} eq 'required' ? FLAG_NAME_OK : + FLAG_ANON_OK | FLAG_NAME_OK ; $flags |= FLAG_DEFAULT_ARGS if $type->{default_arguments}; - $flags |= FLAG_CHECK_NARGS if $type->{check_argument_count}; + $flags |= FLAG_CHECK_NARGS if $type->{check_argument_count}; + $flags |= FLAG_CHECK_TARGS if $type->{check_argument_types}; + $flags |= FLAG_INVOCANT if $type->{invocant}; + $flags |= FLAG_NAMED_PARAMS if $type->{named_parameters}; + $flags |= FLAG_TYPES_OK if $type->{types}; + $flags |= FLAG_RUNTIME if $type->{runtime}; $^H{HINTK_FLAGS_ . $kw} = $flags; $^H{HINTK_SHIFT_ . $kw} = $type->{shift}; $^H{HINTK_ATTRS_ . $kw} = $type->{attrs}; + $^H{HINTK_REIFY_ . $kw} = $type->{reify_type} // 0; $^H{+HINTK_KEYWORDS} .= "$kw "; } } @@ -134,6 +212,68 @@ sub unimport { } +our %metadata; + +sub _register_info { + my ( + $key, + $declarator, + $invocant, + $invocant_type, + $positional_required, + $positional_optional, + $named_required, + $named_optional, + $slurpy, + $slurpy_type, + ) = @_; + + my $info = { + declarator => $declarator, + invocant => defined $invocant ? [$invocant, $invocant_type] : undef, + slurpy => defined $slurpy ? [$slurpy , $slurpy_type ] : undef, + positional_required => $positional_required, + positional_optional => $positional_optional, + named_required => $named_required, + named_optional => $named_optional, + }; + + $metadata{$key} = $info; +} + +sub _mkparam1 { + my ($pair) = @_; + my ($v, $t) = @{$pair || []} or return undef; + Function::Parameters::Param->new( + name => $v, + type => $t, + ) +} + +sub _mkparams { + my @r; + while (my ($v, $t) = splice @_, 0, 2) { + push @r, Function::Parameters::Param->new( + name => $v, + type => $t, + ); + } + \@r +} + +sub info { + my ($func) = @_; + my $key = _cv_root $func or return undef; + my $info = $metadata{$key} or return undef; + require Function::Parameters::Info; + Function::Parameters::Info->new( + keyword => $info->{declarator}, + invocant => _mkparam1($info->{invocant}), + slurpy => _mkparam1($info->{slurpy}), + (map +("_$_" => _mkparams @{$info->{$_}}), glob '{positional,named}_{required,optional}') + ) +} + 'ok' __END__ @@ -146,7 +286,7 @@ Function::Parameters - subroutine definitions with parameter lists =head1 SYNOPSIS - use Function::Parameters; + use Function::Parameters qw(:strict); # simple function fun foo($bar, $baz) { @@ -154,7 +294,9 @@ Function::Parameters - subroutine definitions with parameter lists } # function with prototype - fun mymap($fun, @args) :(&@) { + fun mymap($fun, @args) + :(&@) + { my @res; for (@args) { push @res, $fun->($_); @@ -168,305 +310,463 @@ Function::Parameters - subroutine definitions with parameter lists method set_name($name) { $self->{name} = $name; } - - # function with default arguments + + # 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 default arguments - method skip($amount = 1) { - $self->{position} += $amount; + + # method with named parameters + method resize(:$width, :$height) { + $self->{width} = $width; + $self->{height} = $height; } - -=cut - -=pod - - # use different keywords - use Function::Parameters { - proc => 'function', - meth => 'method', - }; - my $f = proc ($x) { $x * 2 }; - meth get_age() { - return $self->{age}; + $obj->resize(height => 4, width => 5); + + # function with named optional parameters + fun search($haystack, :$needle = qr/^(?!)/, :$offset = 0) { + ... } + + my $results = search $text, offset => 200; =head1 DESCRIPTION -This module lets you use parameter lists in your subroutines. Thanks to -L it works without source filters. +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. -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. +=head2 Basics -=head2 Basic stuff +The anatomy of a function (as recognized by this module): -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. +=over -The effect of C is as if you'd written -C, i.e. the parameter list is simply -copied into L and initialized from L<@_|perlvar/"@_">. +=item 1. -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. +The keyword introducing the function. -=head2 Customizing the generated keywords +=item 2. -You can customize the names of the keywords injected into your scope. To do -that you pass a reference to a hash mapping keywords to types in the import -list: +The function name (optional). - use Function::Parameters { - KEYWORD1 => TYPE1, - KEYWORD2 => TYPE2, - ... - }; +=item 3. -Or more concretely: +The parameter list (optional). - use Function::Parameters { proc => 'function', meth => 'method' }; # -or- - use Function::Parameters { proc => 'function' }; # -or- - use Function::Parameters { meth => 'method' }; # etc. +=item 4. -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 (keywords) can be any identifiers you want -while the values (types) have to be either C<'function'>, C<'method'>, -C<'classmethod'>, or a hash reference (see below). The main difference between -C<'function'> and C<'method'> is that C<'method'>s automatically -L their first argument into C<$self> (C<'classmethod'>s -are similar but shift into C<$class>). +The prototype (optional). -The following shortcuts are available: +=item 5. - use Function::Parameters; - # is equivalent to # - use Function::Parameters { fun => 'function', method => 'method' }; +The attribute list (optional). -=cut +=item 6. -=pod +The function body. + +=back -The following shortcuts are deprecated and may be removed from a future version -of this module: +Example: - # DEPRECATED - use Function::Parameters 'foo'; - # is equivalent to # - use Function::Parameters { 'foo' => 'function' }; + # (1) (2) (3) (4) (5) (6) + fun foo ($x, $y) :($$) :lvalue { ... } + + # (1) (6) + my $f = fun { ... }; -=cut +In the following section I'm going to describe all parts in order from simplest to most complex. -=pod +=head3 Body + +This is just a normal block of statements, as with L|perlsub>. No surprises here. + +=head3 Name + +If present, it specifies the name of the function being defined. As with +L|perlsub>, if a name is present, by default 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 - but see the C flag below). If no name is present, the +declaration is an expression that evaluates to a reference to the function in +question. - # DEPRECATED - use Function::Parameters 'foo', 'bar'; - # is equivalent to # - use Function::Parameters { 'foo' => 'function', 'bar' => 'method' }; +=head3 Attributes -That is, if you want to pass arguments to L, use a -hashref, not a list of strings. +Attributes are relatively unusual in Perl code, but if you want them, they work +exactly the same as with L|perlsub/Subroutine-Attributes>. -You can customize the properties of the generated keywords even more by passing -a hashref instead of a string. This hash can have the following keys: +=head3 Prototype + +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). + +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. + +=head3 Parameter list + +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: =over -=item C +=item 1. Invocant -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. +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: -=item C + method new($class: %init) { + return bless { %init }, $class; + } -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. + method throw($self:) { + die $self; + } -=item C, C +=item 2. Required positional parameters -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: +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: - use Function::Parameters { sub_l => { attributes => ':lvalue' } }; - sub_l foo() { - ... - } + fun add($x, $y) { + return $x + $y; + } + + say add(2, 3); # "5" -turns into +=item 3. Optional positional parameters - sub foo :lvalue { - ... - } +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: -It is recommended that you use C in new code but C is also -accepted for now. + 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"); -=item C +Because default arguments are actually evaluated as part of the function body, +you can also do silly things like this: -Valid values: booleans. This property is on by default, so you have to pass -C<< default_arguments => 0 >> to turn it off. If it is disabled, using C<=> in -a parameter list causes a syntax error. Otherwise it lets you specify -default arguments directly in the parameter list: + fun foo($n = return "nope") { + "you gave me $n" + } + + say foo(2 + 2); # "you gave me 4" + say foo(); # "nope" - fun foo($x, $y = 42, $z = []) { - ... - } +=item 4. Required named parameters -turns into +By putting a colon (C<:>) in front of a parameter you can make it named +instead of positional: - sub foo { - my ($x, $y, $z) = @_; - $y = 42 if @_ < 2; - $z = [] if @_ < 3; - ... - } + 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: -except that none of the parameters are in scope in the expressions that specify -default values. Thus: + rectangle(height => 1, width => 2, height => 2, height => 5); + # same as: rectangle(width => 2, height => 5); - my $var = "outer"; +You can combine positional and named parameters as long as the positional +parameters come first: - fun foo($var, $wat = $var) { - # $wat will default to "outer", not to what was passed - # as the first argument! + fun named_rectangle($name, :$width, :$height) { ... } + + named_rectangle("Avocado", width => 0.5, height => 1.2); -This may change in a future version of this module. +=item 5. Optional named parameters -=item C +As with positional parameters, you can make named parameters optional by +specifying a default argument after an equals sign (C<=>): -Valid values: booleans. This property is off by default. If it is enabled, the -generated code will include checks to make sure the number of passed arguments -is correct (and otherwise throw an exception via L): + fun rectangle(:$width, :$height, :$color = "chartreuse") { + ... + } + + rectangle(height => 10, width => 5); + # same as: rectangle(height => 10, width => 5, color => "chartreuse"); + +=cut - fun foo($x, $y = 42, $z = []) { +=pod + + fun get($url, :$cookie_jar = HTTP::Cookies->new(), :$referrer = $url) { ... } -turns into + my $data = get "http://www.example.com/", referrer => undef; # overrides $referrer = $url - sub foo { - Carp::croak "Not enough arguments for fun foo" if @_ < 1; - Carp::croak "Too many arguments for fun foo" if @_ > 3; - my ($x, $y, $z) = @_; - $y = 42 if @_ < 2; - $z = [] if @_ < 3; - ... +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 + +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: + +=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 + +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: One of the predefined types C, C, +C, C, C, C. +This will set the defaults for all other keys from the specified type, which is +useful if you only want to override some properties: + + use Function::Parameters { defmethod => { defaults => 'method', shift => '$this' } }; + +This example defines a keyword called C that works like the standard +C keyword, but the implicit object variable is called C<$this> instead +of C<$self>. + +Using the string types directly is equivalent to C with no further +customization: + + use Function::Parameters { + foo => 'function', # like: foo => { defaults => 'function' }, + bar => 'function_strict', # like: bar => { defaults => 'function_strict' }, + baz => 'method_strict', # like: baz => { defaults => 'method_strict' }, + }; + +=item C + +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: booleans. If enabled, this keyword takes effect at runtime, not +compile time: + + use Function::Parameters { fun => { defaults => 'function_strict', runtime => 1 } }; + say defined &foo ? "defined" : "not defined"; # not defined + fun foo() {} + say defined &foo ? "defined" : "not defined"; # defined + +C<&foo> is only defined after C has been reached at runtime. + +B A future version of this module may enable C<< runtime => 1 >> by +default for methods. + +=item C + +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 + +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. + +=item C + +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). + +=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>. + +=item C + +Valid values: booleans. If turned on, functions defined with this keyword will +automatically check that the arguments they are passed pass the declared type +constraints (if any). See L below. + +=item C + +Valid values: booleans. This turns on both C and +C. + +=item C + +Valid values: code references. The function specified here will be called to +turn type annotations into constraint objects (see +L below). It will receive two arguments: a string +containing the type description, and the name of the current package. + +The default type reifier is equivalent to: + + sub { + require Moose::Util::TypeConstraints; + Moose::Util::TypeConstraints::find_or_create_isa_type_constraint($_[0]) } =back -Plain C<'function'> is equivalent to: +The predefined type C is equivalent to: { - name => 'optional', + name => 'optional', default_arguments => 1, - check_argument_count => 0, + strict => 0, + invocant => 0, + runtime => 0, } -(These are all default values so C<'function'> is also equivalent to C<{}>.) +These are all default values, so C is also equivalent to C<{}>. -C<'method'> is equivalent to: +C is equivalent to: { - name => 'optional', - default_arguments => 1, - check_argument_count => 0, - attributes => ':method', - shift => '$self', + defaults => 'function', + attributes => ':method', + shift => '$self', + invocant => 1, + # runtime => 1, ## possibly in a future version of this module } -C<'classmethod'> is equivalent to: + +C is equivalent to: { - name => 'optional', - default_arguments => 1, - check_argument_count => 0, - attributes => ':method', - shift => '$class', + defaults => 'method', + shift => '$class', } -=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 function body, -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 available feature -(subroutine name, parameter list, default arguments, prototype, default -attributes, attributes, argument count checks, and implicit C<$self>): - - method foo($x, $y, $z = sqrt 5) :($$$;$) :lvalue :Banana(2 + 2) { - ... - } +C, C, and +C are like C, C, and +C, respectively, but with C<< strict => 1 >>. -And here's what it turns into: +=back - sub foo ($$$;$) :method :lvalue :Banana(2 + 2) { - sub foo ($$$;$); - Carp::croak "Not enough arguments for method foo" if @_ < 2; - Carp::croak "Too many arguments for method foo" if @_ > 4; - my $self = shift; - my ($x, $y, $z) = @_; - $z = sqrt 5 if @_ < 3; - ... - } +Plain C is equivalent to +C<< use Function::Parameters { fun => 'function', method => 'method' } >>. -Another example: +C is equivalent to +C<< use Function::Parameters { fun => 'function_strict', method => 'method_strict' } >>. - my $coderef = fun ($p, $q) :(;$$) - :lvalue - :Gazebo((>:O)) { - ... - }; +=head2 Introspection -And the generated code: +You can ask a function at runtime what parameters it has. This functionality is +available through the function C (which is not +exported, so you have to call it by its full name). It takes a reference to a +function, and returns either C (if it knows nothing about the function) +or a L object describing the parameter list. - my $coderef = sub (;$$) :lvalue :Gazebo((>:O)) { - # vvv only if check_argument_count is enabled vvv - Carp::croak "Not enough arguments for fun (anon)" if @_ < 2; - Carp::croak "Too many arguments for fun (anon)" if @_ > 2; - my ($p, $q) = @_; - ... - }; +Note: This feature is implemented using L, so you'll need to have L +installed if you want to call C (alternatively, if +L is already loaded by the time C is first +called, it will use that instead). + +See L for examples. -=head2 Wrapping Function::Parameters +=head2 Wrapping C -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): +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 (); @@ -475,13 +775,113 @@ and its effects are lexical (i.e. it works like L or L): # or Function::Parameters->import(@custom_import_args); } +=head2 Experimental feature: Types + +An experimental feature is now available: You can annotate parameters with +types. That is, before each parameter you can put a type specification +consisting of identifiers (C), unions (C<... | ...>), and parametric types +(C<...[...]>). Example: + + fun foo(Int $n, ArrayRef[Str | CodeRef] $cb) { ... } + +If you do this, the type reification function corresponding to the keyword will +be called to turn the type (a string) into a constraint object. The default +type reifier simply loads L and forwards to +L|Moose::Util::TypeConstraints/find_or_parse_type_constraint>, +which creates L. + +If you are in "lax" mode, nothing further happens and the types are ignored. If +you are in "strict" mode, C generates code to make sure +any values passed in conform to the type (via +L<< C<< $constraint->check($value) >>|Moose::Meta::TypeConstraint/$constraint->check($value) >>). + +In addition, these type constraints are inspectable through the +L object returned by +L|/Introspection>. + +=head2 Experimental experimental feature: Type expressions + +An even more experimental feature is the ability to specify arbitrary +expressions as types. The syntax for this is like the literal types described +above, but with an expression wrapped in parentheses (C<( EXPR )>). Example: + + fun foo(('Int') $n, ($othertype) $x) { ... } + +Every type expression must return either a string (which is resolved as for +literal types), or a L +(providing C and C methods). + +Note that these expressions are evaluated (once) at parse time (similar to +C blocks), so make sure that any variables you use are set and any +functions you call are defined at parse time. + +=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 BUGS AND INCOMPATIBILITIES + +A future version of this module may enable C<< runtime => 1 >> by default for +methods. If this would break your code, please send me a note or file a bug on +RT. + +=head1 SUPPORT AND DOCUMENTATION + +After installing, you can find documentation for this module with the +perldoc command. + + perldoc Function::Parameters + +You can also look for information at: + +=over + +=item MetaCPAN + +L + +=item RT, CPAN's request tracker + +L + +=item AnnoCPAN, Annotated CPAN documentation + +L + +=item CPAN Ratings + +L + +=item Search CPAN + +L + +=back + +=head1 SEE ALSO + +L + =head1 AUTHOR Lukas Mai, C<< >> =head1 COPYRIGHT & LICENSE -Copyright 2010, 2011, 2012 Lukas Mai. +Copyright 2010-2013 Lukas Mai. This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published