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:
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.
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
.
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?