Reputation: 7443
Can't find a way to get this to work:
sub triple(Str:D $mod where * ~~ any @modifiers = 'command' ) { }
If I don't pass in an argument, I get an error:
Too few positionals passed; expected 1 argument but got 0
With a question mark after $mod
:
sub triple(Str:D $mod? where * ~~ any @modifiers = 'command' ) { }
I get:
Constraint type check failed in binding to parameter '$mod'; expected anonymous constraint to be met but got Str (Str)
Upvotes: 6
Views: 252
Reputation: 32404
TL;DR You've identified the problem in your answer -- precedence -- and provided a solution. This answer covers what happened; why the precedence issue arises; why Raku's grammar/parser didn't just get it right; and lists some solutions, a couple of which I'll start with.
Instead of:
sub triple(Str:D $mod? where * ~~ any @modifiers = 'command' ) { }
I suggest moving the any
and writing one of these:
sub triple(Str:D $mod? where * ~~ @modifiers.any = 'command' ) { }
sub triple(Str:D $mod? where @modifiers.any = 'command' ) { }
The = ...
at the end of the where
clause is parsed as an assignment (to @modifiers
) instead of as a default value (for $mod
):
@modifiers = 'command'
is evaluated, overwriting whatever values @modifiers
had.
The any
creates a junction with one element ('command'
).
Now the only argument that triple
will accept is 'command'
.
Raku's grammar is designed to have nice ergonomics. This includes design details that reduce the need for parens and braces. Overall these design details yield a big net win. But there are wrinkles, and you've encountered one.
Raku lets one write where ...
to specify a where
clause without requiring that one uses an explicit braced lambda ({...}
) for the ...
bit. One can even create a lambda using just a *
. Nice! But where does the lambda end? If you use explicit braces, it's clear. If not, what determines the end of the lambda?
More generally, shouldn't the parser just know that the =
in = 'command'
is not part of any lambda? That it should instead just automatically finish a where
clause if there is one before parsing the = ...
part? That the = ...
should always be parsed as a default value for a parameter?
One can easily see the ambiguity (once one's attention is drawn to it), and so does/could Raku's grammar/parser. It just needs to resolve that ambiguity either by rejecting such syntax, demanding the coder explicitly disambiguates (eg with parens, as you've done), or by choosing which way to parse.
What Raku's grammar/parser does in the face of the ambiguity is choose. And it chooses wrong. (Unless of course one wanted it to be an assignment of some value on the =
's left, not a default for a parameter, though that's gotta be pretty unlikely.)
Why doesn't the parser reject this code as being too ambiguous, or be smart enough to choose the "it's a default" interpretation? It certainly could -- the Raku grammar/parser feature is Turing complete, equivalent in power to the unrestricted grammars category in the Chomsky parsing hierarchy -- so why doesn't it just get it right?
In a nutshell, it gets it right the right amount, at least imo. But that's subjective, oddly worded, and vague, so it's probably not a satisfactory summary. So I'll try provide a bit more detail in the hope it's more informative.
Every Raku design decision is discussed openly, and there are searchable public records of essentially all of it. To dig into these discussions I recommend starting out with Liz++'s awesome IRC log service, and within the numerous channels listed, focusing on the #perl6
logs that ran from 2005 thru 2019 or so.
Although I've been around for many of the Raku design discussions of the last 20 years, I don't have a good recollection of all the discussions surrounding this decision about the ambiguity of meaning of a = ...
at the end of a where
clause, and what to do about it. And I haven't myself recently done the digging I suggest; for now I'll leave that for any interested readers. Instead I will outline what I think will have been some contributing factors:
Single pass parsing
Raku's "braided" approach to language design requires single pass parsing.
Longest parsing orientation
Longest Token Matching is all but essential for user definable braiding (see link in previous point) to be truly viable. LTM reflects a general principle that humans naturally tend toward recognizing the longest "token" (within reason of course). That is to say, if one sees $100
, it strikes one cognitively as a hundred dollars, not as a dollar sign, a 1
, a 0
, and then another 0
.
A similar deal applies to parsing of a string of tokens (again, within reason); if it weren't for the fact one learns to think of = ...
as specifying a parameter's default, the @modifiers = 'command'
would naturally be read as being an assignment into @modifiers
.
Limited backtracking
Backtracking is slow, pathological backtracking utterly evil. So Raku's grammar/parser avoids potentially backtracking in all but three cases for which it really is the right solution, and entirely avoids any risk of pathological backtracking.
Handling ambiguity
While artificial languages can aim to expunge all ambiguity, the closer one gets to eliminating all ambiguity, the greater the amount of extraneous and distracting syntax one requires, such as frequent required use of delimiters (parens, braces, square brackets, etc) to ensure disambiguation. That makes a language increasingly unfriendly and verbose for that reason instead. Raku culture avoids ideological "foolish consistency" extremes.
Raku's designers (principally Larry Wall) considered all these factors and many more and arrived at Raku's solution:
Be rationally predictable
A sufficiently simple and predictable approach to parsing, and requisite sensitivity to the likelihood and costs of any surprises a user may encounter, goes a long way, and the design relative to where
clauses is a case in point.
While the precedence issue may have been a surprise, and the error message unhelpful, I, er, predict you'll find your ERN signal regarding this will tune up quite nicely in fairly short order, just as it will for most of the things that might trip you up as you learn Raku.
Use predictive parsing
While there are several ways to accommodate all of the above, predictive parsing1 is a great choice, and -- not coincidentally! -- the one most naturally written using Raku grammars, and the one used for Raku's own grammar/parser.
Here's what does not work as expected:
sub triple(Str:D $mod? where * ~~ any @modifiers = 'command' ) { }
^ Needs to be end of `where` clause
You've suggested a solution, and I suggested a couple at the start. Some more follow.
You used parens. Here are some other ways to use parens:
sub triple(Str:D $mod? where * ~~ any(@modifiers) = 'command' ) { }
sub triple(Str:D $mod? where * ~~ (any @modifiers) = 'command' ) { }
Or, switch to use of $_
(aka "it") instead of *
(aka "whatever") inside braces:
sub triple(Str:D $mod? where { $_ ~~ any @modifiers } = 'command' ) { }
1 The Wikipedia page discusses "grammars" and "ambiguity" in a manner that may be confusing given that they are not used in the same way those words are used in the context of Raku and of this answer. But discussing that would be a rabbit hole inappropriate for this SO.
Upvotes: 3
Reputation: 7443
Looks like it may have been a precedence problem. This works:
sub triple(Str:D $mod? where (* ~~ any @modifiers) = 'command' ) {}
Upvotes: 5