Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to type-constrain the entries of a Raku function's array argument?

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 Arrays of Ints).

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]).

like image 762
grobber Avatar asked Jun 14 '21 23:06

grobber


Video Answer


2 Answers

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.

like image 150
Elizabeth Mattijsen Avatar answered Oct 31 '22 00:10

Elizabeth Mattijsen


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.

like image 37
codesections Avatar answered Oct 31 '22 02:10

codesections