12

Consider these subroutines that all take a single named parameter. Named parameters should be optional and I haven't seen anything to say there are exceptions to that.

With no type constraints there's no problem; the named parameter is not required. With a type constraint that can accept a type object (no annotation, :U, and :_) there is no problem.

Parameter '$quux' of routine 'quux' must be an object instance of type 'Int', 
not a type object of type 'Int'.  Did you forget a '.new'?
  in sub quux at /Users/brian/Desktop/type.p6 line 16
  in block <unit> at /Users/brian/Desktop/type.p6 line 37

With a type constraint that requires a defined value (annotated with :D) the named parameter is no longer optional. That is, with any of the other definitions I don't have to supply a value. With a :D I must supply a value. I'd rather not leave out the :D because the value I want must be defined.

From the Signatures docs:

Normally, a type constraint only checks whether the value passed is of the correct type.

But, I pass no value. I figured that these constraints would only matter for an assignment. Since I'm not explicitly supplying a value to assign I expected there to be no assignment and no problem. That's not the case with Rakudo 2017.10. This leads me to workaround this in various unsavory ways. This is related to my question Is that one argument or none for a Perl 6 block? where I try to distinguish between zero and one argument cases.

I could work around this by assigning a default value, but in some cases there are no default values that make sense. A Bool is easy, for example, but what definite Int would fit? Whatever it is would be some magical value that would complicate and distract the code. I've done this with Does Perl 6 have an Infinite Int but I get away with that because Inf works as a valid value in that case.

sub foo ( :$foo ) {
    put $foo.defined ?? 'foo defined' !! 'foo not defined';
    }

sub bar ( Int :$bar ) {
    put $bar.defined ?? 'bar defined' !! 'bar not defined';
    }

sub baz ( Int:U :$baz ) {
    put $baz.defined ?? 'baz defined' !! 'baz not defined';
    }

sub quux ( Int:D :$quux ) {
    put $quux.defined ?? 'quux defined' !! 'quux not defined';
    }

sub quack ( Int:_ :$quack ) {
    put $quack.defined ?? 'quack defined' !! 'quack not defined';
    }

foo();
foo( foo => 2 );

bar();
bar( bar => 2 );

baz();
baz( baz => Int );

quack();
quack( quack => 2 );

quux( quux => 2 );
quux();
Elizabeth Mattijsen
  • 25,654
  • 3
  • 75
  • 105
brian d foy
  • 129,424
  • 31
  • 207
  • 592

4 Answers4

8

All parameters always have some value, even if they're optional. I clarified the docs you reference in 379678 and b794a7.

Optional parameters have default default values that are the type object of the explicit or implicit type constraints (implicit constraint is Any for for routines and Mu for blocks).

sub (Int $a?, Num :$b) { say "\$a is ", $a; say "\$b is ", $b }()
# OUTPUT:
# $a is (Int)
# $b is (Num)

Above, the default default values pass the type constraint on the parameters. And the same is the case if you use the :U or :_ type smileys.

However, when you use the :D type smiley, the default default no longer matches the type constraint. If that was left unchecked, you'd lose the benefit of specifying the :D constraint. As the default default type objects would, for example, cause an explosion in the body of this routine, that expects the parameters to be definite values.

sub (Int:D $a?, Num:D :$b) { say $a/$b }()

And to answer the titular question of whether specifying the :D should make parameters required automatically. I'm -1 on the idea, as it introduces a special case users will have to learn just to save a single character of typing (the ! to mark param as required). Doing so also produces a less-helpful error that talks about arity or required parameters, rather than the actual problem: the default default of the parameters failing the typecheck on the parameter.

4

FWIW, a similar issue exists with optional positional parameters:

sub a(Int:D $number?) { ... }
a; # Parameter '$number' of routine 'a' must be an object instance of type 'Int', not a type object of type 'Int'.  Did you forget a '.new'

The same problem occurs if you specify a type object as a default:

sub a(Int:D $number = Int) { ... };
a; #  # Parameter '$number' of routine 'a' must be an object instance of type 'Int', not a type object of type 'Int'.  Did you forget a '.new'

I'm afraid that's just a consequence of the :D constraint when applied to parameters: you just must specify a defined default value for it to be callable without any parameters.

Another approach could of course be the use of a multi sub and required named parameter:

multi sub a() { say "no foo named" }
multi sub a(Int:D :$foo!) { say "foo is an Int:D" }

Hope this helps

Elizabeth Mattijsen
  • 25,654
  • 3
  • 75
  • 105
  • Multi subs that express every combination of parameters is intractable. – brian d foy Jan 09 '18 at 20:04
  • I think instead of saying "optional parameter" the docs should say "implicitly set parameter" or something similar to emphasize that the parameter will exist and it's merely a matter of who sets the value. – brian d foy Jan 09 '18 at 23:43
  • The "optional" in "optional parameter" doesn't mean the parameter is optional. Instead it means that *passing an argument* corresponding to that parameter is optional. – raiph Jan 10 '18 at 03:15
  • That's why you shouldn't say "optional parameter". Remember that people who have no idea what any of this is will read this and think that the docs mean what they say. – brian d foy Jan 11 '18 at 01:17
  • Maybe "optional argument parameter"? Someone could object to that because they think ["argument parameter" is redundant](https://en.wikipedia.org/wiki/Parameter_(computer_programming)#Parameters_and_arguments) or confusing, but the underlying issue is precisely that arguments and parameters are distinct things until after completion of the process of binding arguments to parameters (or not, if they aren't included in a particular call). So any surprise in reaction to "optional argument parameter" is close to perfect as the "WAT?" trigger one would want for a teachable moment about this issue. – raiph Jan 21 '20 at 15:26
3

I solved this by checking for exactly the type Any or smart matching for the one I actually want:

sub f ( :$f where { $^a.^name eq 'Any' or $^a ~~ Int:D } ) {
    put $f.defined ?? "f defined ($f)" !! 'f not defined';
    }

f( f => 5 );
f();

To answer my original question: all parameters are required. It's merely a matter of how they get values. It can be through an argument, an explicit default, or an implicit default (based from its type constraint).

brian d foy
  • 129,424
  • 31
  • 207
  • 592
  • I think I get why you've written "all parameters are required" given the thought process evident in this particular SOQA but it directly contradicts longstanding and reasonable existing usage in the P6 language and its docs (eg [`.optional`](https://docs.perl6.org/routine/optional)) in which both positional and named parameters can be optional or required. – raiph Jan 10 '18 at 03:01
  • 1
    Those definitions apply to how a user specifies _arguments_, but as I've shown it comes into conflicts with other expectations about _parameters_. – brian d foy Jan 10 '18 at 03:08
  • 1
    I think the patch made it worse. I think it's a much larger problem in mental models. – brian d foy Jan 10 '18 at 05:53
  • FWIW, you're doing two smartmatches there (one explicit in the block and another one on its result in the `where`). You can make it ~15% faster by using a thunk instead: `(:$f where .WHAT =:= Any || Int:D)` Since `where` clauses prevent dispatch caching and you're checking .definedness in the body anyway, you can get ~7.8x faster code with just `Int` type constraint and `.DEFINITE` check in the body –  Jan 10 '18 at 15:08
  • > "I think the patch made it worse." Please open a doc Issue, so it could be improved: https://github.com/perl6/doc/issues/new –  Jan 10 '18 at 15:09
  • Checking by the name of the type is not a good way to handle that. This isn't Perl 5 classes can have the same name. You should have used `$^a === Any`. – Brad Gilbert Jan 21 '20 at 19:43
2

Fixing problems with brian's code

sub f ( :$f where { $^a.^name eq 'Any' or $^a ~~ Int:D } ) { ... }

This code is ugly, complicated, and stringly typed for no good reason.

Here's code that fixes those problems:

sub f ( :$f where Any | Int:D ) { ... }

But there are still problems. Using the Any type means a f :f(foo) call, where foo just happens to be Any, will get through.

Instead one can use a type that calling code can't possibly use (presuming the following is declared in a package and used from outside the package):

my role NoArg {}
our sub f ( :$f where Int:D | NoArg = NoArg ) { ... }

This leaves a final problem: the LTA error message inherent in brian's approach.

While the following currently slows the code down, it further improves the code's readability, and the automatically generated error message if an invalid argument is passed is perfectly cromulent (expected OptionalInt but got ...):

my role NoArg {}
my subset OptionalInt where Int:D | NoArg;
our sub f ( OptionalInt :$f = NoArg ) { ... }

It isn't about named parameters

The title of brian's question is:

Why does constraining a Raku named parameter to a definite value make it a required value?

The situation brian describes isn't about named parameters. It's due to the :D constraint. It applies to all parameters, variables, and scalar containers:

sub quux ( Int:D :$quux ) {} # Named parameters require a definite value:
quux;                        # Error.

sub quux ( Int:D $quux? ) {} # Positional parameters do too:
quux;                        # Error

my Int:D $foo;               # And variables: Error.

my Int:D @array;             # And containers:
@array[42] := Nil;           # Error

Raku(do) is being helpful

The :D type "smiley" denotes a Definite value. That's its purpose.

If user code fails to provide a Definite value for any parameter/variable/container with (nothing but) a :D constraint, then Raku(do) logically must do something sensible to maintain type safety. And that's what it's doing.


Consider Raku's escalating type constraints:

  1. If no type constraint is specified, there's no need to insist on a value of any particular type. Raku(do) maintains memory safety, but that's it.

  2. If a parameter/variable/container has an Int (or Int:_) type constraint, Raku(do) ensures any value assigned or bound to it is an Int, but it won't insist it's a definite Int.

  3. If a parameter/variable/container has a :D constraint (eg Int:D), then Raku(do) insists that any value assigned or bound to it is a definite value, or, if it isn't, maintains type safety by either throwing a resumable exception (if safe) or killing the program (if not). One thing it does not do is make up some magical default because, as brian notes in his question:

    [Raku(do)] could work around this by assigning a default value, but ... what definite Int would fit? Whatever it is would be some magical value that would complicate and distract the code.


In the parameter case below, Raku(do) refuses to bind the quux call to the quux sub and maintains type safety by throwing a resumable exception. If a dev wishes to, they can choose to .resume, and on their head be it:

sub quux ( Int:D :$quux? ) { say 42 } # Does NOT display 42
quux;
CATCH { .resume }
say 99;                               # 99

In the variable case, Rakudo may again be able to maintain memory safety:

my Int:D $quux = 42;
$quux = Nil;
CATCH { .say; .resume }  # Type check failed ...
say $quux;               # 42

Or it may not; in extremis Raku(do) fails with an unrecoverable error at compile-time:

my Int:D $quux; # "Error while compiling ... requires an initializer"
CATCH { .resume }
say 99;         # Does NOT display 99

In this instance Raku(do) must bail out during compilation to avoid the magic value problem.

It's not true that "all parameters are required"

In his answer brian says "all parameters are required".

As I explained above, this isn't just about parameters, but rather variables and scalar containers too.

Does it make sense to say that all parameters, all variables, and all containers are required? What does that even mean?

brian's mental model flatly contradicts Raku's syntax:

sub foo ($bar is required, $baz?) { ... }

Is $bar required? Yes. Is $baz required? No.


Imagine a web form that has two fields: a username field with a *required annotation, and a password field without it, or perhaps with an *optional annotation. How many required fields does this form contain? Most folk would say it contains one. This is the mental model Raku is following in regard to the words "required" and "optional".

If the person writing the code behind the form specifies that the password field must contain a definite value, then it can't end up blank. Perhaps the code injects a default "my_password" to satisfy the definite value constraint. Again, this is the mental model Raku is following regarding a :D constraint.


Yes, it's possible folk will be confused by "optional parameter" and "required parameter". brian clearly was. So, maybe we use "argument parameters" at least once or twice in the doc? I'm not convinced it'll help.

Also, if a dev writes an "optional argument parameter" but also specifies a :D constraint, the issues described in this SO still apply. The change to calling parameters "argument parameters" won't have helped.

I think the Rakoon consensus is that the onus is on those who think they have better wording, to show and tell. My response to brian's "all parameters are required" is "there is always a well-known solution to every human problem — neat, plausible, and wrong". But perhaps you, dear reader, have a better idea?

raiph
  • 31,607
  • 3
  • 62
  • 111