New Method::Signatures: named parameters!

schwern on 2008-10-08T14:28:27

The next version of Method::Signatures on its way to CPAN now has named parameters.

method display( $text is ro, :$justify = 'left', :$enchef = 0 ) {
    ...
}

# $text = "some stuff", $justify = 'left', $enchef = 0 $obj->display( "some stuff" );

# $text = "Things and stuff\n", $justify = 'left', enchef = 1 $obj->display( <<'TEXT', enchef => 1 ); Things and stuff TEXT


Because Perl 5 lacks the extra syntax that Perl 6 has to disambiguate named from positional arguments, and frankly I don't think it's worth it, there's some restrictions on their use to avoid ambiguous situations. They always have to come at the end of the signature.

method foo( :$this, :$that, $foo?, $bar? )     # illegal

# $foo = "this", $bar = 42 or $this = 42 ? $obj->foo( this => 42 );


Technically, one could allow *required* named params to come first. I could also probably get away with allowing optional named params coming before required positionals by defining a precedence... but it rapidly gets shaky and I'd rather not open up that complexity unless there's some great need.

You can't mix optional positional params with named params.

method bar( $a, $b?, :$c )  # illegal, ambiguous

# $a = 'c', $b = 42 or is it $c = 42? $obj->bar( c => 42 );


Again, I could resolve this ambiguity by saying that positional parameters take precedence over named params. Then the above becomes $a = 'c' and $b = 42, but again I don't feel a pressing need to open up that bit of complexity.

Anyhow, I'm pooped. That's the last of the major features for this sprint before presenting at PPW and I think it's pretty darn useful.

Next is a lot of refactoring work. The signature parser has gotten messy, named parameters in particular proved more complicated than they should be. There's almost no error checking and I found when I tried to add some that I don't have enough information to report on compile time errors. There's no way for a user to introspect the signature, though there is an internal signature structure which will be pretty easy to adapt to the task. All the sugar syntax has to be turned into equivalent traits (ie. '?$foo' and '$foo is optional' are equivalent) to make introspection easier. And errors should be thrown as proper error objects instead of just strings.

And Florian and I have to consolidate the guts of our modules into a more generic "add block syntax to Perl" module.


two more common uses

rjbs on 2008-10-13T14:03:26

It's be keen to see two more things supported.

First, it'd be nice to say, "Yes, I just want named arguments, but they're all in $_[1] as a hashref. The rest is empty."

  $obj->method({ named => 'arguments' });

Secondly, "Yeah, I have positional and named arguments, with all the named arguments in a hashref at the end."

  $obj->method($arg1, $arg2, { named => 'args' });

This would cover nearly all my code's methods.

(Maybe I should STFU and WSFC.)

Re:two more common uses

schwern on 2008-10-13T18:32:37

To the hash ref, I don't deny it's common, but I ask why do we do that? One reason is to try and save a copy, if the hash happens to be huge. But they aren't, and if they are do you really want to enumerate all the names? And anyway, breaking the hash ref down into a bunch of scalars wastes all that memory savings anyway.

Another reason is to leave open the possibility of passing in more arguments.

$obj->method({ this => 'that' }, 42);

in which case you're not really passing in named arguments else that new positional argument could just be a named one.

$obj->method({ this => 'that', stuff => 42 });

And you no longer need the hash ref, just make it a hash.

Otherwise, what you're really passing in that hash ref is not named arguments but a lump of data. For example...

method new ($class: \%data, $something_else) {
    my $self = bless {%data}, $class;
 
    $self->wibble($something_else);
}

To chop up %data into a bunch of scalars makes no sense.

As for the hash-ref at end, unless you have optional positional params its no longer necessary.

method foo ($this, $that, :$named) {
    ...
}
 
$obj->method($arg1, $arg2, named => 'args');

Of course, I'm sure I'm missing something important and TMTOWTDI reigns. And there's no real reason it can't handle named arguments as hash refs. The real block is always deciding what the syntax should be. Since it's a good idea to make the signature look like how it's called...

method foo( $this, $that, {:$foo, :$bar} );

Which opens up the possibility of allowing the named hash-ref params coming first.

method foo( {:$foo, :$bar}, $this, $that );

Or even the special case "slurp the hash ref off the end, then resolve the possibly optional positionals".

method foo( $this?, $that?, {:$foo, :$bar} );

In order for any of this to work and remain unambiguous, the hash ref would be required by default.

Re:two more common uses

rjbs on 2008-10-14T19:56:53

Apart from anything else, the biggest argument is:

If I don't have to change the calling semantics, I can actually use this in old code. If I have to update every caller to pass a list rather than a hashref, I am not going to do it. If I can't update my old codebase to use this, I am not likely to start using it for new code, either.

Re:two more common uses

Aristotle on 2008-10-19T22:59:49

One reason is to try and save a copy

The only good reason is that it forces an “Odd number of elements” warning to be emitted from the calling site without having to add any code to the callee. Obviously this does not apply in your module’s case.