Making Sequences work for you
こんにちは!
VASILYのiOSエンジニアにこらすです。 SwiftのコントリビューターとSwift EvolutionSE-0053の作者です。 他のOSSプロジェクトにも貢献してるので興味があればGithubでフォローしてください。
今回のトピックはSwift2.0以降のSequenceType
というプロトコルと、その内部的な動きについて紹介します。class
やstruct
をSequenceType
プロトコルに準拠させると、for in
ループやmap
, filter
などを使えるようになります。
さあ、始めましょう!
struct Unique<T: Comparable> { private var backingStore: [T] = [] var count: Int { return backingStore.count } func setHas(element: T) -> Bool { return backingStore.filter { $0 == element }.count != 0 } mutating func append(element: T) { guard !setHas(element) else { return } backingStore.append(element) } }
このUnique
という構造体はとてもシンプルです。Comparable
に準拠した型の要素をappend
すると内部的な配列に要素を追加します。配列中に同じ要素が存在する場合は追加しません。
それではUnique
をテストしてみましょう!
var mySet = Unique<String>() // Our set can mySet.setHas("A Careless Cat") //false mySet.append("A Careless Cat") mySet.setHas("A Careless Cat") //true mySet.append("A Careless Cat") //すでにある文字列 mySet.count //まだ1です! //もうちょっと項目追加しましょう! mySet.append("A Dangerous Dog") mySet.append("A Diamond Dog") mySet.append("Petty Parrot") mySet.append("North American Reckless Raccoon") mySet.append("A Monadic Mole")
動物は十分入ったので、名前をprint
してみよう!
for animal in mySet { println(animal) }
あれ?うまくいきませんね。。。:
なぜうまくいかなかったかというと、for in
を使うために必要な実装が足りないからです。
Stringの配列でfor in
を書くのは、下記の書き方と同じです。
let vowels = ["A","E","I","O","U"] var gen = vowels.generate() while let letter = gen.next() { print(letter) }
generate()
はSequenceType
プロトコルのメソッドです。
Unique
はSequenceType
プロトコルに準拠してないのでfor in
が動かないのです。
それでは、SequenceType
プロトコルに必要な条件を見て実装してみましょう!
public protocol SequenceType { associatedtype Generator : GeneratorType public func generate() -> Self.Generator }
generate()
の戻り値の型は型推論が効くので、typealias Generator = ...
を書く必要はありません。
Unique
をSequenceType
プロトコルに準拠させて、generate()
メソッドを実装するべきですが、まだGeneratorType
のことをよく知りません。
それではGeneratorType
をチェックしましょう!
Generator とは?
public protocol GeneratorType { associatedtype Element public mutating func next() -> Self.Element? }
SequenceType
と同じように一つのメソッドしかありません。next()
の戻り値の型は、Element
型となっていますが型推論が効きます。[String]
であれば、実際にはString
になります。
Generator
はnext()
メソッドで、保持しているデータを順番に返します。最後のデータを返したあと再度next()
を呼ぶとnil
を返します。
GeneratorType
を使い終わると再利用できません。同じデータセットをもう一度GeneratorType
で読みたければ、新しくGeneratorType
を生成する必要があります。
そして、generate()
を実装する時に戻り値のGeneratorType
と他のGeneratorType
変数の状態を共有しないようにしなければいけません。
基本的な上限があるGeneratorType
:
struct CountToGenerator: GeneratorType { private var limit: Int private var currentCount = 0 init(limit: Int) { self.limit = limit } mutating func next() -> Int? { guard currentCount < limit else { return nil } defer { currentCount += 1 } return currentCount } } var goTillTen = CountToGenerator(limit: 10) while let num = goTillTen.next() { print(num) }
SequenceType
とGeneratorType
を学んだので、Unique
専用のGeneratorType
を作りましょう!
class UniqueGenerator<T>: GeneratorType { private var _generationMethod: () -> T? init(_generationMethod: () -> T?) { self._generationMethod = _generationMethod } func next() -> T? { return _generationMethod() } } extension Unique: SequenceType { func generate() -> UniqueGenerator<T> { var iteration = 0 return UniqueGenerator { if iteration < self.backingStore.count { let result = self.backingStore[iteration] iteration += 1 return result } return nil } } }
ここまで学んだことで、Unique
のGeneratorType
の実装ができましたが、もう少し短く書けます。
AnyGenerator
というGenericタイプを使うとUniqueGenerator
を作る必要がありません。
extension Unique: SequenceType { func generate() -> AnyGenerator<T> { var iteration = 0 return AnyGenerator { if iteration < self.backingStore.count { let result = self.backingStore[iteration] iteration += 1 return result } return nil } } }
もう一度for in
ループを書きましょう!
for item in mySet { print(item) }
やっと動きました!
map
とfilter
も動きます!やった!
let cnt = mySet.map { Int($0.characters.count) } //[14, 15, 13, 12, 31, 14] mySet.filter { $0.characters.first != "A" } //["Petty Parrot", "North American Reckless Raccoon"]
Controlling Sequences with Sequences
SequenceType
の実装次第で、とても大きいリストや無限リストを作ることができます。
そういったリストから先頭からn
個取り出そうとすると、そのまま使うと無限ループになってしまいます。
例えば下記のThePattern
は無限リストです。
for in
で使うと、Generator
がnil
を返さないため、永遠に0
か1
を返します。
class ThePattern: SequenceType { func generate() -> AnyGenerator<Int> { var isOne = true return AnyGenerator { isOne = !isOne return isOne ? 1 : 0 } } } // 無限ループ for i in ThePattern() { print(i) }
この無限リストから
class First<S: SequenceType>: SequenceType { private let limit: Int private var counter: Int = 0 private var generator: S.Generator init(_ limit: Int, sequence: S) { self.limit = limit self.generator = sequence.generate() } func generate() -> AnyGenerator<S.Generator.Element> { return AnyGenerator { defer { self.counter += 1 } guard self.counter < self.limit else { return nil } return self.generator.next() } } } for item in First(5, sequence: ThePattern()) { print(item) // 0 1 0 1 0 }
いいですね!
First(n, sequence: s)
を呼び出すと、s
の先頭n
個の要素を取り出せます。
s
が無限リストだとしても最初のn
個しかチェックしないので効率的です。
まとめ
配列のように扱うクラスやコンテナクラスであればSequenceType
プロトコルを採用しましょう。
コードが読みやすくなりますし、自然にfor in
やmap
, filter
を使えるようになります。
ぜひ気軽にSequenceType
のプロトコルを使ってみてください。
最後に
VASILYではSwift好きなiOSエンジニアを募集しています!興味があればぜひ応募してみてください。 https://www.wantedly.com/projects/27397www.wantedly.com