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

问题描述

我试图在 Raku 中定义一个子例程,它的参数是 ArrayInt(将其作为约束,即拒绝 不是 ArrayInt 个)。

问题:实现这一目标的“最佳”方式是什么?

示例在 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,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,3])
Constraint type check Failed in binding to parameter '@a'; expected anonymous constraint to be met but got Array ([1,3])
  in sub f at <unkNown file> line 1
  in block <unit> at <unkNown file> line 1

尽管

> [1,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

我过去使用过它,但它让我觉得有点笨拙/冗长..

补充说明

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

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

> class A { has Int @.a }
(A)

> A.new(a => [1,3])
A.new(a => Array[Int].new(1,3))

> A.new(a => [1,'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,3 # or (1,3),or [1,3]
> f(@a)
1

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

解决方法

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

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

#1

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

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

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

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

#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,3])

这与签名绑定不同。有效地在 .new 中:

@!a = [1,3]

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

my Int @a = [1,"foo"]

失败。

,

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

首先,请注意 os.systemsub f(Int @a) {...} 之间还有一个区别:第一个检查数组的类型(O(1) 操作),而第二个遍历数组并检查数组的类型每个元素的类型(O(n) 操作)。这是在 previous question 中提出的,您可能会觉得有用。

其次,还有另一种编写 sub f(@a where .all ~~ Int) {...} 的方法,它利用了新的强制协议(这可能是我个人的编写方式):

f

这将 $a 限制为可以转换为 sub f(Array[Int]() $a) {...} 的任何类型,然后将其绑定到该类型的数组。这与 Array[Int] 大致相似,不同之处在于它使用 @a where .all ~~ Int 并在函数内部维护类型约束。

,

另一种可能...

sub f(Array[Int]() $array) { $array }
say WHAT f [1,3]; # (Array[Int])

或者:

sub f2(Array[Int]() $array;; @array = $array) { @array }
say WHAT f2 [1,3]; # (Array[Int])