The Iterator
and Iterable
roles§
Raku 是一种函数式语言,但函数在处理复杂数据结构时需要一些东西来保存。特别是,它们需要一个统一的接口,可以以相同的方式应用于所有数据结构。其中一种接口由 Iterator
和 Iterable
角色提供。
Iterable
角色相对简单。它提供了一个 iterator
方法的占位符,该方法是 for
等语句实际使用的。for
会在它前面的变量上调用 .iterator
,然后为每个项目运行一个块。其他方法,如数组赋值,将使 Iterable
类以相同的方式运行。
does Iterable;my = DNA.new('ACGTACGTT');say .raku;# OUTPUT: «[("A", "C", "G"), ("T", "A", "C"), ("G", "T", "T")]»say ».join("").join("|"); # OUTPUT: «ACG|TAC|GTT»
在这个例子中,它是 Iterable
中示例的扩展,展示了 for
如何调用 .iterator
,只有当创建的对象被分配给 Positional
变量 @longer-chain
时,iterator
方法才会在适当的上下文中被调用;这个变量是一个 Array
,我们在最后一个例子中像这样操作它。
Iterator
角色(可能有点令人困惑的名字)比 Iterable
更复杂。首先,它提供了一个常量 IterationEnd
。然后,它还提供了一系列方法,例如 .pull-one
,它允许在多个上下文中进行更精细的迭代操作:添加或删除项目,或跳过它们以访问其他项目。事实上,该角色为所有其他方法提供了默认实现,因此唯一需要定义的方法正是 pull-one
,该方法只由该角色提供了一个占位符。虽然 Iterable
提供了循环将要使用的顶层接口,但 Iterator
提供了在循环的每次迭代中将要调用的底层函数。让我们用这个角色扩展前面的例子。
does Iterable does Iterator;my := DNA.new('GAATCC');.say for ; # OUTPUT: «(G A A)(T C C)»
我们声明一个 DNA
类,它执行两个角色,Iterator
和 Iterable
;该类将包含一个字符串,该字符串将被限制为长度为 3 的倍数,并且仅由 ACGT 组成。
让我们看一下 pull-one
方法。每次发生新的迭代时都会调用它,因此它必须保持上次迭代的状态。$.index
属性将在调用之间保存该状态;pull-one
将检查链的末尾是否已到达,并将返回角色提供的 IterationEnd
常量。事实上,实现这个底层接口简化了 Iterable
接口的实现。现在迭代器将是对象本身,因为我们可以调用它的 pull-one
来依次访问每个成员;因此 .iterator
将只返回 self
;这是可能的,因为该对象将同时是 Iterable
和 Iterator
。
这并不总是必须的,在大多数情况下,.iterator
将不得不构建一个要返回的迭代器类型(例如,它将跟踪迭代状态,我们现在在主类中这样做),就像我们在前面的例子中所做的那样;但是,这个例子展示了构建一个满足迭代器和可迭代角色的类的最小代码。
如何迭代:上下文和主题变量§
for
和其他循环将每次迭代产生的项目放入 主题变量 $_
中,或者将它们捕获到与块一起声明的变量中。这些变量可以通过使用 ^
twigil 在循环内部直接使用,而无需声明它们。
当使用 序列运算符 时,会发生隐式迭代。
say 1,1,1, … * > 300; # OUTPUT: «(1 1 1 4 7 16 46 127 475)
生成块只运行一次,而完成序列的条件(在本例中是项大于 300)没有满足。这具有运行循环的副作用,但也创建了一个输出的列表。
这可以通过使用 gather/take
块 更系统地完成,它们是一种不同类型的迭代结构,而不是在 sink 上下文中运行,而是每次迭代返回一个项目。这个 Advent Calendar 教程 解释了这种循环的用例;事实上,gather 并不是一个循环结构,而是一个收集 take
生成的项目并从中创建列表的语句前缀。
Classic
循环以及我们不喜欢它们的原因§
经典的 for
循环,使用循环变量递增,可以在 Raku 中通过 loop
关键字 完成。其他 repeat 和 while 循环也是可能的。
但是,一般来说,它们是不鼓励的。Raku 是一种函数式和并发语言;在 Raku 中编码时,你应该以函数式的方式看待循环:逐个处理迭代器产生的项目,也就是说,将一个项目馈送到一个块中,而没有任何副作用。这种函数式视图也允许通过 hyper
或 race
自动线程方法轻松地并行化操作。
如果您更习惯使用传统的循环,该语言允许您使用它们。但是,在 Raku 中,尽可能使用函数式和并发迭代结构被认为是最佳实践。
注意:从 6.d 版本开始,循环可以从最后一个语句的值生成一个值列表。