如何对 Raku 函数的数组参数的条目进行类型约束?

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

我正在尝试在 Raku 中定义一个子例程,其参数是 Array of Ints(将其作为约束,即拒绝 不是 Arrays 共 Ints).

问题:实现该目标的“最佳”(最惯用的,或最直接的,或您认为 'best' 应该是什么意思)的方法是什么?

Raku REPL 中的示例 运行 如下。

我希望能奏效

> 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

另一个无效的例子

> 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

即使

> [1,2,3].all ~~ Int
True

什么有效

两种变体

> sub f(@a where { @a.all ~~ Int }) {1}

> sub f(@a where { $_.all ~~ Int }) {1}

给我想要的东西:

> 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

我过去用过它,但它给我的印象有点 clumsy/verbose..

补充说明

我最初尝试的语法 Int @a 并不完全是假的,但我不知道它什么时候应该通过,什么时候不应该通过。

例如,我可以在 class 中执行此操作:

> 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

编辑 1

根据 the docs,这有效

> sub f(Int @a) {1}
&f

> my Int @a = 1,2,3 # or (1,2,3), or [1,2,3]
> f(@a)
1

但是如果我在 @a 的声明中省略了 Int 我会回到之前报告的错误。如前所述,我不能在匿名数组上使用 运行 f f([1,2,3]).

我认为主要的误解是 my Int @a = 1,2,3[1,2,3] 在某种程度上是等价的。他们不是。第一种情况定义了一个数组,它将 Int 值。第二种情况定义了一个数组,它可以接受任何东西,并且恰好有 Int 个值。

我将尝试涵盖您尝试过的所有版本,它们为什么不起作用,以及它将如何 起作用。我将使用裸 dd 作为已到达函数主体的证明。

#1

sub f(Int @a) { dd }
f([1,2,3])

这不起作用,因为签名接受一个 Positional,而该 Positional 在其容器描述符上有一个 Int 约束。签名绑定仅查看参数的约束查看值。观察:

my Int @a; say @a.of;   # (Int)
say [1,2,3].of;         # (Mu)
say Mu ~~ Int;          # False

这种方法没有解决方案,因为没有 [ ] 语法可以生成带有 Int 约束的 Array

#2

sub f(@a where *.all ~~ Int) { dd }

这非常接近,但是 * 的使用并不正确。我不确定这是否是一个错误。

您发现这些解决方案也有效:

sub f(@a where { @a.all ~~ Int }) { dd }
sub f(@a where { $_.all ~~ Int }) { dd }

幸运的是,您 不必 实际指定显式块。这也有效:

sub f(@a where @a.all ~~ Int) { dd }
sub f(@a where $_.all ~~ Int) { dd }

除了您找到的 @a.all$_.all 解决方案外,还有第三种解决方案:只需扔掉 *!

sub f(@a where .all ~~ Int) { dd }

#3

class A { has Int @.a }
A.new(a => [1,2,3])

这与签名绑定不同。实际上在 .new 你正在做一个:

@!a = [1,2,3]

之所以可行,是因为您指定的数组中只有 Int 个值。正如你所展示的,如果里面还有其他东西,它就会失败。但这与:

没有区别
my Int @a = [1,2,"foo"]

失败。

这是对 Liz 已经接受的正确答案的补充。

首先,请注意 sub f(Int @a) {...}sub f(@a where .all ~~ Int) {...} 之间还有一个区别:第一个检查数组的类型(O(1) 操作),而第二个遍历数组和检查每个元素的类型(O(n) 操作)。这出现在 中,您可能会发现它有帮助。

其次,还有另一种编写 f 的方法,它利用了新的强制协议(我个人可能会这样写):

sub f(Array[Int]() $a) {...}

这将 $a 限制为可以转换为 Array[Int] 的任何类型,然后将其绑定到该类型化数组。这与 @a where .all ~~ Int 大致相似,只是它使用 $ 并在函数内部维护类型约束。