Swift で Phantom Type (幽霊型)

  • 124
    Like
  • 2
    Comment
More than 1 year has passed since last update.

社内の「Scala 勉強会」で Phantom Type (幽霊型) という厨二心をくすぐる感じのデザインパターンを教えてもらったので、同じことを Swift でもやってみました。

インスタンスの状態を変数ではなく 型パラメータ として持つことで、状態チェックを実行時ではなく コンパイル時 に行えるというイカしたテクニックです。

class Status{}
class NotReady: Status{}
class Ready: Status{}

class Something<T: Status> {
    static func createInstance() -> Something<NotReady> {
        return Something<NotReady>()
    }

    func readify() -> Something<Ready> {
        return Something<Ready>()
    }
}

extension Something where T: Ready {
    func shout() {
        print("phantom types are awesome!")
    }
}

StatusNotReady, Ready をサブクラスに持つだけの空クラス(これが幽霊型)で、SomethingStatus 型の型パラメータ T を持っています。Something には where T: Ready という 型パラメータ制約付きの拡張 によって、shout() という関数を追加しています。

createInstance()Something<NotReady> インスタンスが作られ、readify() を呼ぶと新たに Something<Ready> インスタンスが作られます。そうして作られた T: Ready のインスタンスに対してのみ shout() を呼ぶコードが書けるのです。

やってみましょう:

let s = Something.createInstance()
s.shout() // error: 'NotReady' is not a subtype of 'Ready'

ちゃんと コンパイルエラー が出ました!
一方で readify() を呼ぶと…

let s = Something.createInstance()
s.readify().shout() // phantom types are awesome!

エラーもなく、ちゃんと実行もできました!

こうすることで コンパイラレベル で間違った状態での処理を防げるので、そのためのテストも作らなくて済むことになります。

Phantom Type カッコイイですね :trollface::thumbsup: