AnySequenceってなんなの?
AnySequence
ってなんなの?っていうのを調べようとしたメモです。
JavaのGenericsは本当に型情報を消し去るイレイジャなのでまず意識しなくていいし、C#(4.0以上)のGenericsは共変性・反変性を持っているので、Swiftだとなんでややこしいコードになるのかよくわからなかったのです。
まず、SequenceType
プロトコルに準拠した独自のクラスを作ることを考えます。
// 本 struct Book { var name: String } // 本棚 final class BookShelf: SequenceType { private var books: [Book] = [] func append(book: Book) { self.books.append(book) } // Swift 3.0 だと AnyIterator とかいうストレートな名前になるはず func generate() -> AnyGenerator<Book> { var index : Int = 0 return AnyGenerator<Book> { // index++ を Swift 3.0 準拠で書くとこうなる、微妙… defer { index = index.successor() } return index < self.books.count ? self.books[index] : .None } } }
SequenceType
に準拠することで、自作のクラスでもfor-in
構文を利用できます。
let bookShelf = BookShelf() bookShelf.append(Book(name: "ハイペリオン")) bookShelf.append(Book(name: "ハイペリオンの没落")) bookShelf.append(Book(name: "エンディミオン")) bookShelf.append(Book(name: "エンディミオンの覚醒")) for book in bookShelf { print(book.name) }
ここまでは簡単です。
問題は、「BookShelf
という実装詳細を隠したい場合はどうすればいいか?」と考えたときです。SequenceType
プロトコルとして扱うという素直なアプローチは使えません。
let sequence: SequenceType = bookShelf
以下のコンパイルエラーになります。
error: protocol 'SequenceType' can only be used as a generic constraint because it has Self or associated type requirements
これがなぜかというと、SequenceType
の定義を見ると分かるように、
public protocol SequenceType { associatedtype Generator : GeneratorType associatedtype SubSequence // 略
SequenceType
は付属型(associatedtype
になって意味がわかりやすくなりましたね!)を持っているためです。Swiftの仕様上、付属型を持つプロトコルはgenericの制約としてしか使えません。
GeneratorType
とSubSequence
を具体化したクラスが必要です。そこで、AnySequence
の出番です。
let sequence: AnySequence<Book> = AnySequence(bookShelf) for book in sequence { print(book.name) }
AnySequence
でラップすることで、BookShelf
の実装詳細を隠蔽することができました。
ただし同じElement
の型でまとめるのが限界です。AnySequence<AnyObject>
にAnySequence<Book>
を突っ込むみたいな融通は効かない(共変性とか反変性みたいなのはない)です。
ポケモン型消去
この辺の説明をポケモンを使ってすっごいわかりやすく説明したのが、ポケモン型消去の話です。
以下、Sequence
よりもポケモンの方が分かりやすそうなので、ポケモンで考えます。
この説明ですが、初見ではAnyPokemon
がattack
の実装をイニシャライザで退避させている理由がわかりませんでした。
class AnyPokemon <PokemonType>: Pokemon { private let _attack: ((PokemonType) -> Void) required init<U:Pokemon where U.PokemonType == PokemonType>(_ pokemon: U) { _attack = pokemon.attack } func attack(type:PokemonType) { return _attack(type) } }
これは実際に手を動かせば答えはすぐに出ました。PokemonType
はAnyPokemon
に渡すポケモンの型に束縛される―たとえばイニシャライザにAnyPokemon<Electric>
のピカチュウを渡して初めて、attack
の型がattack(type :Electric) -> ()
に決定される―からです。
先の説明の通り、付随型を持つプロトコルのままポケモンを保持することはできないので、束縛された後の実装を保持するようにしているのです。でもプロトコルのすべての実装を、イニシャライザで退避させるようなAny
クラスを作るのって正直エレガントだと思えません。
そこで、AnySequence
がどうやってSequenceType
の各処理を移譲しているのか調べようと考えたのです。
AnySequenceは言語仕様内で型安全と型消去を両立している
下調べしたとき、Swift 2.1のバグにちょうどそんなのがあったのでちょろいと思ったのですよ。
AnySequence
のイニシャライザでは、元のシーケンスをさらに_SequenceBox
にラップして処理を移譲していたのですが、S.Generator.Element
とS.SubSequence.Generator.Element
との型が別になるような、変なGenerator
を実装した場合に、移譲がうまくいかないのでイニシャライザに制約を加えたとのこと。
だから、_SequenceBox
の実装を探して終わりだと思ったんですけど、これは自動生成されているボイラーコードなので単純には見つかりませんでした。
自動生成されているコードを、ポケモンの例に置き換えるとたぶんこんな感じになるのだと思います。
繰り返しますがSwiftでは付随型を持つPokemon
プロトコルを型として持てません。
そこでまず付随型をジェネリクスにした_AnyPokemonBase
という抽象クラスを作り、型として保持できるようにします。もちろん、Swiftの言語仕様に抽象クラスがあるわけではないので、空実装を提供した「抽象クラスっぽい何か」です。
そしてその_AnyPokemonBase
を継承した_AnyPokemonBox
に実際のポケモンをラップします。実際のPokemon
プロトコルの処理はここから移譲します。
2段階ラップして、抽象クラス経由で操作することで、型消去したい型を保持する(日本語が迷走してる)、というようなことを実現しています。Pokemon
プロトコルに機能が増えた場合にはボイラーコードを書くことになることには変わりありませんし(StdLibはこの辺は自動化している)、素直にabstract
なクラスが言語仕様にあった方が幸せな気がするのですが、仕組みとしてはうまいと思いました。
AnySequence
の場合、内部にAnyGenerator
を持っているのでより複雑ですが、ほぼ同じ構造です。きっと。
なお、以下の記事を大変参考にしました。
ほぼ真似と言っても過言ではありません。こんなの自力で辿り着けないです…。