型消去の話で出てきたポケモンの例題を理解する #tryswiftconf

  • 137
    いいね
  • 3
    コメント
この記事は最終更新日から1年以上が経過しています。

http://www.tryswiftconf.com/

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 と確定しているので何の問題もない。ところが、 p2Pokemon であって 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 AnyPokemonattack を使えるようにする。
private let _pokemon: Pokemon はできない(先述の通りgenericなPokemonTypeが抽象的なまま解決されない)。

あとは適当にタイプとかわざとか実装してやればポケモンマスター間違いなし。

実例

ikesyoさんが実例を出してました。

https://github.com/Carthage/Commandant/blob/0.8.3/Sources/Commandant/Command.swift#L32-L66

Carthageの struct CommandWrapper も、 class AnyPokemon 同様に

  • イニシャライザで適当なインスタンスを受け取って型を確定させる
  • インスタンス自体ではなくそれの持つフィールドやメソッドを控える

ということをやっている。役割としては Any〜 だけど、実装面からすると確かに 〜Wrapper がしっくりくる……気がする。