From: Lukas Mai Date: Sun, 28 Oct 2012 01:54:27 +0000 (+0100) Subject: rewrite documentation X-Git-Tag: v0.10_03~2 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=81203272d60f51f5ace76395996bf9618686ef80;p=p5sagit%2FFunction-Parameters.git rewrite documentation --- diff --git a/lib/Function/Parameters.pm b/lib/Function/Parameters.pm index ba2b481..07660bd 100644 --- a/lib/Function/Parameters.pm +++ b/lib/Function/Parameters.pm @@ -168,7 +168,7 @@ Function::Parameters - subroutine definitions with parameter lists =head1 SYNOPSIS - use Function::Parameters; + use Function::Parameters qw(:strict); # simple function fun foo($bar, $baz) { @@ -198,349 +198,383 @@ Function::Parameters - subroutine definitions with parameter lists return bless { %init }, $class; } - # function with default arguments + # 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 Function::Parameters qw(:strict); - fun greet($x) { - print "Hello, $x\n"; - } + $obj->resize(height => 4, width => 5); - greet "foo", "bar"; - # Dies at runtime with "Too many arguments for fun greet" + # function with named optional parameters + fun search($haystack, :$needle = qr/^(?!)/, :$offset = 0) { + ... + } - greet; - # Dies at runtime with "Not enough arguments for fun greet" + 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 - # use different keywords - use Function::Parameters { - proc => 'function', - meth => 'method', - }; +=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 { ... } - 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 -=head2 Basic stuff +This is just a normal block of statements, as with L|perlsub>. No surprises here. -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. +=head3 Name -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/"@_">. +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. -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. +=head3 Attributes -=head2 Customizing the generated keywords +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 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: +=head3 Prototype - use Function::Parameters { - KEYWORD1 => TYPE1, - KEYWORD2 => TYPE2, - ... - }; +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). -Or more concretely: +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. - use Function::Parameters { proc => 'function', meth => 'method' }; # -or- - use Function::Parameters { proc => 'function' }; # -or- - use Function::Parameters { meth => 'method' }; # etc. +=head3 Parameter list -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 a hash reference (see below) or -C<'function'>, C<'method'>, C<'classmethod'>, C<'function_strict'>, -C<'method_strict'>, or C<'classmethod_strict'>. 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 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: -The following shortcuts are available: +=over - use Function::Parameters; - # is equivalent to # - use Function::Parameters { fun => 'function', method => 'method' }; +=item 1. Invocant -=cut +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: -=pod + method new($class: %init) { + return bless { %init }, $class; + } + + method throw($self:) { + die $self; + } - use Function::Parameters ':strict'; - # is equivalent to # - use Function::Parameters { fun => 'function_strict', method => 'method_strict' }; +=item 2. Required positional parameters -=pod +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: -The following shortcuts are deprecated and may be removed from a future version -of this module: + 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"); - # DEPRECATED - use Function::Parameters 'foo'; - # is equivalent to # - use Function::Parameters { 'foo' => 'function' }; +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) { + ... + } - # DEPRECATED - use Function::Parameters 'foo', 'bar'; - # is equivalent to # - use Function::Parameters { 'foo' => 'function', 'bar' => 'method' }; + my $data = get "http://www.example.com/", referrer => undef; # overrides $referrer = $url -That is, if you want to create custom keywords with L, -use a hashref, not a list of strings. +The above example shows that passing any value (even C) will override +the default argument. -You can tune the properties of the generated keywords even more by passing -a hashref instead of a string. This hash can have the following keys: +=item 6. Slurpy parameter -=over +Finally you can put an array or hash in the parameter list, which will gobble +up the remaining arguments (if any): -=item C + 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") -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. +If you combine this with named parameters, the slurpy parameter will end up +containing all unrecognized keys: -=item C + fun bar(:$size, @whatev) { ... } + + bar weight => 20, size => 2, location => [0, -3]; + # $size = 2, @whatev = ('weight', 20, 'location', [0, -3]) -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. +=back -=item C +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. -Valid values: booleans. This lets users of this keyword specify an explicit -invocant, that is, the first parameter may be followed by a C<:> (colon) -instead of a comma and will by initialized by shifting the first element off -C<@_>. +=head3 Keyword -You can combine C and C, in which case the variable named in -C serves as a default shift target for functions that don't specify an -explicit invocant. +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: -=item C, C +=over -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: +=item C - use Function::Parameters { sub_l => { attributes => ':lvalue' } }; - sub_l foo() { - ... - } +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. -turns into +=item C - sub foo :lvalue { - ... - } +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. -It is recommended that you use C in new code but C is also -accepted for now. +=item C<< use Function::Parameters { KEYWORD1 => TYPE1, KEYWORD2 => TYPE2, ... } >> -=item C +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: -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: +=over - fun foo($x, $y = 42, $z = []) { - ... - } +=item C -turns into +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). - sub foo { - my ($x, $y, $z) = @_; - $y = 42 if @_ < 2; - $z = [] if @_ < 3; - ... - } +=item C -You can even refer to previous parameters in the same parameter list: +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. - print fun ($x, $y = $x + 1) { "$x and $y" }->(9); # "9 and 10" +=item C -This also works with the implicit first parameter of methods: +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. - method scale($factor = $self->default_factor) { - $self->{amount} *= $factor; - } +=item C -=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). -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): +=item C - fun foo($x, $y = 42, $z = []) { - ... - } +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. -turns into +=item C - 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; - ... - } +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: +The predefined type C is equivalent to: { name => 'optional', + invocant => 0, default_arguments => 1, check_argument_count => 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<'function_strict'> is like C<'function'> but with -C<< check_argument_count => 1 >>. - -C<'method'> is equivalent to: +C is equivalent to: { name => 'optional', - default_arguments => 1, - check_argument_count => 0, - attributes => ':method', shift => '$self', invocant => 1, + attributes => ':method', + default_arguments => 1, + check_argument_count => 0, } -C<'method_strict'> is like C<'method'> but with -C<< check_argument_count => 1 >>. -C<'classmethod'> is equivalent to: +C is equivalent to: { name => 'optional', - default_arguments => 1, - check_argument_count => 0, - attributes => ':method', shift => '$class', invocant => 1, + attributes => ':method', + default_arguments => 1, + check_argument_count => 0, } -C<'classmethod_strict'> is like C<'classmethod'> but with -C<< check_argument_count => 1 >>. - -=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> overriden -by an explicit invocant declaration): - - method foo($this: $x, $y, $z = sqrt 5) - :($$$;$) - :lvalue - :Banana(2 + 2) - { - ... - } - -And here's what it turns into: - - sub foo ($$$;$) :method :lvalue :Banana(2 + 2) { - sub foo ($$$;$); - Carp::croak "Not enough arguments for method foo" if @_ < 3; - Carp::croak "Too many arguments for method foo" if @_ > 4; - my $this = shift; - my ($x, $y, $z) = @_; - $z = sqrt 5 if @_ < 3; - ... - } - -Another example: +C, C, and +C are like C, C, and +C, respectively, but with C<< check_argument_count => 1 >>. - my $coderef = fun ($p, $q) - :(;$$) - :lvalue - :Gazebo((>:O)) { - ... - }; +=back -And the generated code: +Plain C is equivalent to +C<< use Function::Parameters { fun => 'function', method => 'method' } >>. - 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) = @_; - ... - }; +C is equivalent to +C<< use Function::Parameters { fun => 'function_strict', method => 'method_strict' } >>. -=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 L (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 (); @@ -549,6 +583,23 @@ L). # 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<< >>