3.9 KiB
イテレータ
イテレータは、コンテナの要素を取り出すためのオブジェクトです。
for! 0..9, i =>
print! i
このコードは0から9までの数字を出力します。
それぞれの数字(=Intオブジェクト)はi
に代入され、=>
以下の動作(=print! i
)が実行されます。このような繰り返し実行のことを イテレーション といいます。
ではここでfor!
プロシージャの型シグネチャを見てみましょう。
for!: |T: Type, I <: Iterable T| (I, T => None) => None
第一引数はIterable
という型のオブジェクトを受け付けるようです。
Iterable
は.Iterator
属性, .iter
メソッドを要求メソッドに持つ型です。
Iterable T = Trait {
.Iterator = {Iterator}
.iter = (self: Self) -> Self.Iterator T
}
.Iterator
属性の型{Iterator}
はいわゆるセットカインド(カインドの説明はこちら)です。
assert [1, 2, 3] in Iterable(Int)
assert 1..3 in Iterable(Int)
assert [1, 2, 3].Iterator == ListIterator
assert (1..3).Iterator == RangeIterator
log [1, 2, 3].iter() # <ListIterator object>
log (1..3).iter() # <RangeIterator object>
ListIterator
とRangeIterator
はどちらもIterator
を実装するクラスで、List
, Range
にイテレーション機能を与えるためだけに存在します。
このようなデザインパターンをコンパニオンクラス1と呼びます。
そしてIteratorImpl
パッチがイテレーション機能のコアです。Iterator
は.next
メソッド1つだけを要求し、IteratorImpl
は実に数十個のメソッドを提供します。ListIterator
やRangeIterator
は.next
メソッドを実装するだけでIteratorImpl
の実装メソッドを使うことができるわけです。この利便性から、標準ライブラリでは多数のイテレータが実装されています。
classDiagram
class List~T~ {
...
iter() ListIterator~T~
}
class Range~T~ {
...
iter() RangeIterator~T~
}
class Iterable~T~ {
<<trait>>
iter() Iterator~T~
}
Iterable~T~ <|.. List~T~: Impl
Iterable~T~ <|.. Range~T~: Impl
class ListIterator~T~ {
array: List~T~
next() T
}
class RangeIterator~T~ {
range: Range~T~
next() T
}
class Iterator~T~ {
<<trait>>
next() T
}
Iterator~T~ <|.. ListIterator~T~: Impl
Iterator~T~ <|.. RangeIterator~T~: Impl
List <-- ListIterator
Range <-- RangeIterator
Iterable
のような、トレイト(この場合はIterator
)を静的ディスパッチでありながら統一的に扱えるインターフェースを提供する型をコンパニオンクラスアダプターと呼びます。
1 このパターンには統一された名前がないようであるが、Rustではcompanion struct patternと呼ばれており、それになぞらえて命名した。↩