I am trying to define a subroutine in Raku
whose argument is, say, an Array of Ints (imposing that as a constraint, i.e. rejecting arguments that are not Array
s of Int
s).
Question: What is the "best" (most idiomatic, or straightforward, or whatever you think 'best' should mean here) way to achieve that?
Examples run in the Raku
REPL follow.
What I was hoping would work
> sub f(Int @a) {1}
&f
> f([1,2,3])
Type check failed in binding to parameter '@a'; expected Positional[Int] but got Array ([1, 2, 3])
in sub f at <unknown file> line 1
in block <unit> at <unknown file> line 1
Another non-working example
> sub f(@a where *.all ~~ Int) {1}
&f
> f([1,2,3])
Constraint type check failed in binding to parameter '@a'; expected anonymous constraint to be met but got Array ([1, 2, 3])
in sub f at <unknown file> line 1
in block <unit> at <unknown file> line 1
even though
> [1,2,3].all ~~ Int
True
What does work
Both variations
> sub f(@a where { @a.all ~~ Int }) {1}
and
> sub f(@a where { $_.all ~~ Int }) {1}
do give me what I want:
> f([5])
1
> f(['x'])
Constraint type check failed in binding to parameter '@a'; expected anonymous constraint to be met but got Array (["x"])
in sub f at <unknown file> line 1
in block <unit> at <unknown file> line 1
I've used that in the past, but it strikes me as a bit clumsy/verbose..
Additional remark
The syntax Int @a
I initially tried isn't completely bogus, but I can't tell when it's supposed to go through and when not.
I can do this in a class for instance:
> class A { has Int @.a }
(A)
> A.new(a => [1,2,3])
A.new(a => Array[Int].new(1, 2, 3))
> A.new(a => [1,2,'x'])
Type check failed in assignment to @!a; expected Int but got Str ("x")
in block <unit> at <unknown file> line 1
Edit 1
As per the docs, this works
> sub f(Int @a) {1}
&f
> my Int @a = 1,2,3 # or (1,2,3), or [1,2,3]
> f(@a)
1
But if I omit the Int
in the declaration of @a
I get back to the errors reported before. And as noted, I can't run f
on anonymous arrays, with f([1,2,3])
.
I think the main misunderstanding is that my Int @a = 1,2,3
and [1,2,3]
are somehow equivalent. They are not. The first case defines an array that will only take Int
values. The second case defines an array that will take anything, and just happens to have Int
values in it.
I'll try to cover all versions you tried, why they didn't work, and possibly how it would work. I'll be using a bare dd
as proof that the body of the function was reached.
#1
sub f(Int @a) { dd }
f([1,2,3])
This doesn't work because the signature accepts a Positional
that has an Int
constraint on its container descriptor. Signature binding only looks at the constraints of arguments, not at the values. Observe:
my Int @a; say @a.of; # (Int)
say [1,2,3].of; # (Mu)
say Mu ~~ Int; # False
There is no solution to this approach, as there is no [ ]
syntax that generates an Array
with an Int
constraint.
#2
sub f(@a where *.all ~~ Int) { dd }
This is very close, but the use of *
is not doing the right thing. I'm not sure whether this is a bug or not.
You've found these solutions also work:
sub f(@a where { @a.all ~~ Int }) { dd }
sub f(@a where { $_.all ~~ Int }) { dd }
Fortunately, you do not have to actually specify the explicit block. This also works:
sub f(@a where @a.all ~~ Int) { dd }
sub f(@a where $_.all ~~ Int) { dd }
Apart from the @a.all
and $_.all
solutions you found, there is a third solution: just throw out the *
!
sub f(@a where .all ~~ Int) { dd }
#3
class A { has Int @.a }
A.new(a => [1,2,3])
This is different from signature binding. Effectively in the .new
you're doing a:
@!a = [1,2,3]
And that works because there are only Int
values in the array that you specified. As you showed, of there's anything else in there, it will fail. But that is no different from:
my Int @a = [1,2,"foo"]
failing.
This is intended as a supplement to Liz's already-accepted and correct answer.
First, note that there's one additional difference between sub f(Int @a) {...}
and sub f(@a where .all ~~ Int) {...}
: the first checks the type of the Array (an O(1) operation) while the second iterates through the Array and checks the type of each element (an O(n) operation). This came up in a previous question, which you might find helpful.
Second, there's another way to write f
that takes advantage of the new coercion protocol (and is probably how I'd write it, personally):
sub f(Array[Int]() $a) {...}
This restricts $a to any type that can be converted to an Array[Int]
and then binds it to that typed Array. This is broadly similar to @a where .all ~~ Int
, except that it uses a $
and maintains the type constraint inside the function.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With