1日目お疲れ様でした!あの人やこの人や皆さんが渋谷に集結して熱かったですね。
以下、Gwendolyn Westonさんの発表「Keep Calm and Type Erase On」のコードをもとに勝手に解釈しました。解釈が間違ってたらご指摘ください……
発表そのものの解釈ではないのでそれについては録画の方にお願いします。コードの完全版はすでに上がっています↓
https://gist.github.com/gwengrid/d8aacf2118fa12c9b475
ポケモン
ポケモンを考える。ポケモンの要件は「何らかの属性を持つ」「その属性の攻撃をする」とすると
protocol Pokemon {
typealias PokemonType
func attack(move: PokemonType) -> Void
}
と表すことができる。 protocol Pokemon<T>
は不可。仕様。また、ポケモンというのは哺乳類とかと同じ感じなので class Pokemon<T>
も不適切。
moveってなんだと思ったらポケモンの"わざ"を指すらしい。
ポケモンのわざ:move
わざマシン:TM(technical machine)
ひでんマシン:HM(hidden machine)
ピカチュウ
ポケモンとひとくちにいってもいろいろな種族のポケモンがいる。ピカチュウはでんきタイプのポケモンである。これを実装すると
class Electric { /*略*/ }
class Pikachu: Pokemon {
typealias PokemonType = Electric
func attack(move: Electric) -> Void {}
}
である。ピカチュウはでんきタイプなので、 attack(move: PokemonType)
の引数としては Electric
型のわざである10まんボルトなどが想定される。ピカチュウ、10まんボルトだ!
早速このクラスを用いピカチュウを数匹作り出す。が、これでは問題が起きる。
// OK
let p0 = Pikachu()
let p1: Pikachu = Pikachu()
// NG
let p2: Pokemon = Pikachu()
p0
および p1
の型は Pikachu
であり PokemonType
というgenericなものは Electric
と確定しているので何の問題もない。ところが、 p2
は Pokemon
であって PokemonType
が抽象的なままなのでエラーとなる。
つまり、「ポケモンならなんでも入れられる型」を持つ定数が作れないという問題が起こる。 class Pokemon<T>
ではないので Pokemon<Electric>
として「でんきタイプならなんでも入れられる型」にするという手段も取れない。
特定タイプのポケモンをなんでも入れられる型
というわけで class AnyPokemon<PokemonType>: Pokemon
を作る。
完成形は
var e: AnyPokemon<Electric> = AnyPokemon(Pikachu())
e = AnyPokemon(Raichu())
というふうに、イニシャライザで何らかのポケモンのインスタンスを取るようにする。
これでいくと、上記で作った var e: AnyPokemon<Electric>
に対し、くさタイプであるフシギダネ(Bulbasaur)を持ってきて e = AnyPokemon(Bulbasaur())
とすることはできない。
具体的にはこうなる↓
class AnyPokemon<PokemonType>: Pokemon {
private let _attack: ((PokemonType) -> Void)
required init<U:Pokemon where U.PokemonType == PokemonType>(_ pokemon: U) {
_attack = pokemon.attack
}
func attack(move: PokemonType) {
return _attack(move)
}
}
イニシャライザで attack
を _attack
に控えておく。そして class AnyPokemon
が attack
を使えるようにする。
private let _pokemon: Pokemon
はできない(先述の通りgenericなPokemonTypeが抽象的なまま解決されない)。
あとは適当にタイプとかわざとか実装してやればポケモンマスター間違いなし。
実例
ikesyoさんが実例を出してました。
Carthageの struct CommandWrapper
も、 class AnyPokemon
同様に
- イニシャライザで適当なインスタンスを受け取って型を確定させる
- インスタンス自体ではなくそれの持つフィールドやメソッドを控える
ということをやっている。役割としては Any〜
だけど、実装面からすると確かに 〜Wrapper
がしっくりくる……気がする。