X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FFunction%2FParameters.pm;h=a39f93bbbf273190ef46388f4c29f59062e13258;hb=42e595b0d7841bfda100f6af7115ea41cc46b818;hp=78eb6963bd260fefcac5d2811afb90b361f518b7;hpb=b4fcf7d0d142ac56f81c83eca891a5ca2684de18;p=p5sagit%2FFunction-Parameters.git diff --git a/lib/Function/Parameters.pm b/lib/Function/Parameters.pm index 78eb696..a39f93b 100644 --- a/lib/Function/Parameters.pm +++ b/lib/Function/Parameters.pm @@ -1,251 +1,302 @@ package Function::Parameters; -use strict; +use v5.14.0; use warnings; -our $VERSION = '0.03'; +use Carp qw(confess); -use Devel::Declare; -use B::Hooks::EndOfScope; -use B::Compiling; +use XSLoader; +BEGIN { + our $VERSION = '1.0202'; + XSLoader::load; +} -sub guess_caller { - my ($start) = @_; - $start ||= 1; +sub _assert_valid_identifier { + my ($name, $with_dollar) = @_; + my $bonus = $with_dollar ? '\$' : ''; + $name =~ /^${bonus}[^\W\d]\w*\z/ + or confess qq{"$name" doesn't look like a valid identifier}; +} - my $defcaller = (caller $start)[0]; - my $caller = $defcaller; +sub _assert_valid_attributes { + my ($attrs) = @_; + $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}; +} - for (my $level = $start; ; ++$level) { - my ($pkg, $function) = (caller $level)[0, 3] or last; - #warn "? $pkg, $function"; - $function =~ /::import\z/ or return $caller; - $caller = $pkg; - } - $defcaller +sub _reify_type_default { + require Moose::Util::TypeConstraints; + Moose::Util::TypeConstraints::find_or_create_isa_type_constraint($_[0]) } -sub _fun ($) { $_[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 => {}, # all default settings + function_strict => { + defaults => 'function', + strict => 1, + }, + method => { + defaults => 'function', + attributes => ':method', + 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; - my $keyword = shift; - my $caller = guess_caller; - #warn "caller = $caller"; - Devel::Declare->setup_for( - $caller, - { $keyword => { const => \&parser } } - ); + 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]}; + } - no strict 'refs'; - *{$caller . '::' . $keyword} = \&_fun; -} + my %spec; -sub report_pos { - my ($offset, $name) = @_; - $name ||= ''; - my $line = Devel::Declare::get_linestr(); - substr $line, $offset + 1, 0, "\x{20de}\e[m"; - substr $line, $offset, 0, "\e[31;1m"; - print STDERR "$name($offset)>> $line\n"; -} + my $bare = 0; + for my $proto (@_) { + my $item = ref $proto + ? $proto + : [$proto, $bare_arms[$bare++] || confess(qq{Don't know what to do with "$proto"})] + ; + my ($name, $proto_type) = @$item; + _assert_valid_identifier $name; -sub parser { - my ($declarator, $start) = @_; - my $offset = $start; - my $line = Devel::Declare::get_linestr(); - - my $fail = do { - my $_file = PL_compiling->file; - my $_line = PL_compiling->line; - sub { - my $n = $_line + substr($line, $start, $offset - $start) =~ tr[\n][]; - die join('', @_) . " at $_file line $n\n"; - } - }; + $proto_type = {defaults => $proto_type} unless ref $proto_type; - my $atomically = sub { - my ($pars) = @_; - sub { - my $tmp = $offset; - my @ret = eval { $pars->(@_) }; - if ($@) { - $offset = $tmp; - die $@; - } - wantarray ? @ret : $ret[0] + 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 $try = sub { - my ($pars) = @_; - my @ret = eval { $pars->() }; - if ($@) { - return; - } - wantarray ? @ret : $ret[0] - }; + my %clean; + + $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} // ''; + _assert_valid_identifier $clean{shift}, 1 if $clean{shift}; + + $clean{attrs} = join ' ', map delete $type{$_} // (), qw(attributes attrs); + _assert_valid_attributes $clean{attrs} if $clean{attrs}; + + $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; + } - my $skipws = sub { - #warn ">> $line"; - my $skip = Devel::Declare::toke_skipspace($offset); - if ($skip < 0) { - $skip == -$offset or die "Internal error: offset=$offset, skip=$skip"; - Devel::Declare::set_linestr($line); - return; + $clean{reify_type} = $index; } - $line = Devel::Declare::get_linestr(); - #warn "toke_skipspace($offset) = $skip\n== $line"; - $offset += $skip; - }; - $offset += Devel::Declare::toke_move_past_token($offset); - $skipws->(); - my $manip_start = $offset; + %type and confess "Invalid keyword property: @{[keys %type]}"; - my $name; - if (my $len = Devel::Declare::toke_scan_word($offset, 1)) { - $name = substr $line, $offset, $len; - $offset += $len; - $skipws->(); + $spec{$name} = \%clean; } + + for my $kw (keys %spec) { + 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 + ; + $flags |= FLAG_DEFAULT_ARGS if $type->{default_arguments}; + $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 "; + } +} - my $scan_token = sub { - my ($str) = @_; - my $len = length $str; - substr($line, $offset, $len) eq $str or $fail->(qq{Missing "$str"}); - $offset += $len; - $skipws->(); - }; - - my $scan_id = sub { - my $len = Devel::Declare::toke_scan_word($offset, 0) or $fail->('Missing identifier'); - my $name = substr $line, $offset, $len; - $offset += $len; - $skipws->(); - $name - }; - - my $scan_var = $atomically->(sub { - (my $sigil = substr($line, $offset, 1)) =~ /^[\$\@%]\z/ or $fail->('Missing [$@%]'); - $offset += 1; - $skipws->(); - my $name = $scan_id->(); - $sigil . $name - }); - - my $separated_by = $atomically->(sub { - my ($sep, $pars) = @_; - my $len = length $sep; - defined(my $x = $try->($pars)) or return; - my @res = $x; - while () { - substr($line, $offset, $len) eq $sep or return @res; - $offset += $len; - $skipws->(); - push @res, $pars->(); - } - }); - - my $many_till = $atomically->(sub { - my ($end, $pars) = @_; - my $len = length $end; - my @res; - until (substr($line, $offset, $len) eq $end) { - push @res, $pars->(); - } - @res - }); - - my $scan_params = $atomically->(sub { - if ($try->(sub { $scan_token->('('); 1 })) { - my @param = $separated_by->(',', $scan_var); - $scan_token->(')'); - return @param; - } - $try->($scan_var) - }); - - my @param = $scan_params->(); - - my $scan_pargroup_opt = sub { - substr($line, $offset, 1) eq '(' or return ''; - my $len = Devel::Declare::toke_scan_str($offset); - my $res = Devel::Declare::get_lex_stuff(); - Devel::Declare::clear_lex_stuff(); - $res eq '' and $fail->(qq{Can't find ")" anywhere before EOF}); - $offset += $len; - $skipws->(); - "($res)" - }; +sub unimport { + my $class = shift; - my $scan_attr = sub { - my $name = $scan_id->(); - my $param = $scan_pargroup_opt->() || ''; - $name . $param - }; + if (!@_) { + delete $^H{+HINTK_KEYWORDS}; + return; + } - my $scan_attributes = $atomically->(sub { - $try->(sub { $scan_token->(':'); 1 }) or return '', []; - my $proto = $scan_pargroup_opt->(); - my @attrs = $many_till->('{', $scan_attr); - ' ' . $proto, \@attrs - }); + for my $kw (@_) { + $^H{+HINTK_KEYWORDS} =~ s/(?(); - my $attr = @$attributes ? ' : ' . join(' ', @$attributes) : ''; - $scan_token->('{'); +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, + }; - my $manip_end = $offset; - my $manip_len = $manip_end - $manip_start; - #print STDERR "($manip_start:$manip_len:$manip_end)\n"; + $metadata{$key} = $info; +} - my $params = @param ? 'my (' . join(', ', @param) . ') = @_;' : ''; - #report_pos $offset; - $proto =~ tr[\n][ ]; +sub _mkparam1 { + my ($pair) = @_; + my ($v, $t) = @{$pair || []} or return undef; + Function::Parameters::Param->new( + name => $v, + type => $t, + ) +} - if (defined $name) { - my $pkg = __PACKAGE__; - #print STDERR "($manip_start:$manip_len) [$line]\n"; - substr $line, $manip_start, $manip_len, " do { sub $name$proto; sub $name$proto$attr { BEGIN { ${pkg}::terminate_me(q[$name]); } $params "; - } else { - substr $line, $manip_start, $manip_len, " sub$proto$attr { $params "; +sub _mkparams { + my @r; + while (my ($v, $t) = splice @_, 0, 2) { + push @r, Function::Parameters::Param->new( + name => $v, + type => $t, + ); } - #print STDERR ".> $line\n"; - Devel::Declare::set_linestr($line); + \@r } -sub terminate_me { - my ($name) = @_; - on_scope_end { - my $line = Devel::Declare::get_linestr(); - #print STDERR "~~> $line\n"; - my $offset = Devel::Declare::get_linestr_offset(); - substr $line, $offset, 0, " \\&$name };"; - Devel::Declare::set_linestr($line); - #print STDERR "??> $line\n"; - }; +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}') + ) } -1 +'ok' __END__ +=encoding UTF-8 + =head1 NAME 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->($_); @@ -254,51 +305,575 @@ 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; =head1 DESCRIPTION -This module lets you use parameter lists in your subroutines. Thanks to -L it works without source filters. - -WARNING: This is my first attempt at using 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 Basic stuff - -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 parens. This -list consists of comma-separated variables. - -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/"@_">. - -=head2 Advanced stuff - -If you need L, you can -put them after the parameter list with their usual syntax. There's one -exception, though: you can only use one colon (to start the attribute list); -multiple attributes have to be separated by spaces. - -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). - -Normally, Perl subroutines are not in scope in their own body, meaning the -parser doesn't know the name C or its prototype when processing -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. +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. + +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 { ... } + + # (1) (6) + my $f = fun { ... }; + +In the following section I'm going to describe all parts in order from simplest to most complex. + +=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. + +=head3 Attributes + +Attributes are relatively unusual in Perl code, but if you want them, they work +exactly the same as with L|perlsub/Subroutine-Attributes>. + +=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 1. Invocant + +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 + +As with positional parameters, you can make named parameters optional by +specifying a default argument after an equals sign (C<=>): + + 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 + +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 + +The predefined type C is equivalent to: + + { + name => 'optional', + default_arguments => 1, + strict => 0, + invocant => 0, + runtime => 0, + } + +These are all default values, so C is also equivalent to C<{}>. + +C is equivalent to: + + { + defaults => 'function', + attributes => ':method', + shift => '$self', + invocant => 1, + # runtime => 1, ## possibly in a future version of this module + } + + +C is equivalent to: + + { + defaults => 'method', + shift => '$class', + } + +C, C, and +C are like C, C, and +C, respectively, but with C<< strict => 1 >>. + +=back + +Plain C is equivalent to +C<< use Function::Parameters { fun => 'function', method => 'method' } >>. + +C is equivalent to +C<< use Function::Parameters { fun => 'function_strict', method => 'method_strict' } >>. + +=head2 Introspection + +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. + +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 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(@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 @@ -306,7 +881,7 @@ Lukas Mai, C<< >> =head1 COPYRIGHT & LICENSE -Copyright 2009 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