Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

はじめて Phantom と遭遇して、闇雲に闘いを挑んでみた話 #kbkz_tech

1,829 views

Published on

iOSDC でも話題に登った Phantom Type という存在が気になって、その得体知れないものを知るために、闇雲に特徴を探ってみたお話です。

Published in: Software

はじめて Phantom と遭遇して、闇雲に闘いを挑んでみた話 #kbkz_tech

  1. 1. #kbkz_tech
  2. 2. 
 

  3. 3. class HogeOperation { private(set) var isPrepared = false func prepare() { guard !isPrepared else { fatalError("prepare を複数回呼ばないこと") } isPrepared = true } }
  4. 4. // Init 状態の HogeOperation を生成 let operation = HogeOperation<Init>() // 最初は prepared を呼べて準備完了したものを取得可能 let preparedOp = operation.prepared() // 準備が終われば prepared はビルドエラーで実行不可 preparedOp.prepared()
  5. 5. // これで "操作状態を表現する型" を表現 protocol OperationState {} // 操作状態ごとにクラスを定義 class Init: OperationState {} class Prepared: OperationState {}
  6. 6. // 型パラメーターで型に状態を付与 class HogeOperation<State: OperationState> { /* 今回は内部で、 型パラメーターを使わないのがポイント */ }
  7. 7. extension HogeOperation where State: Init { // 準備を実行し、準備完了状態のインスタンスを返す func prepared() -> HogeOperation<Prepared> {…} } extension HogeOperation where State: Prepared { // 目的の操作を実行する func execute() {…} }
  8. 8. let operation = HogeOperation<Init>() // Init 状態では、まだ execute は存在しない operation.execute() // prepared を呼ぶことで Prepared 状態のものを取得 let preparedOp = operation.prepared() // Prepared 状態には、もう prepared は存在しない preparedOp.prepared()
  9. 9. let operation = HogeOperation<Init>() // HogeOperation<Init> 型だから prepared が呼べる let preparedOp: HogeOperation<Prepared>() = operation.prepared() // HogeOperation<Prepared> 型だから execute が呼べる preparedOp.execute() // Init クラスや Prepared クラスを、実行時には使わない // ビルドの段階で、もう役目が済んでいる
  10. 10. let operation = HogeOperation<Init>() let preparedOp = operation.prepared() let type1 = type(of: operation) let type2 = type(of: preparedOp) type1 == type2 // false
  11. 11. // 型パラメーターで型に状態を付与 class HogeOperation<State: OperationState> { /* 内部では、型パラメーターを使っていない 型の在り方を説明するためだけに使っている */
  12. 12. /// Phantom Type に出逢う前の認識 // Array はテンプレート的なもので… struct Array<Element> { } // 型パラメーターによって、異なる型になる let values: Array<Int> = Array<String>()
  13. 13. // protocol OperationState {} // class Init: OperationState {} // class Prepared: OperationState {}
  14. 14. class Operation { fileprivate var data: OperationData fileprivate init(data: OperationData) { self.data = data } func mob() {} }
  15. 15. class OperationInit: Operation { convenience init() {…} func prepared() -> OperationPrepared {…} } class OperationPrepared: Operation { func execute() {…} }
  16. 16. let operation = OperationInit() let preparedOp = operation.prepared() preparedOp.execute()
  17. 17. struct OperationInit { fileprivate var data: OperationData fileprivate init(data: OperationData) {…} // 共通機能 func mob() {} // 固有の機能 init() {…} func prepared() -> OperationPrepared {…} }
  18. 18. struct OperationPrepared { fileprivate var data: OperationData fileprivate init(data: OperationData) {…} // 共通機能 func mob() {} // 固有の機能 func execute() {…} }
  19. 19. let operation = OperationInit() let preparedOp = operation.prepared() preparedOp.execute()
  20. 20. protocol Operation {} struct OperationInit: Operation { … } struct OperationPrepared: Operation { … }
  21. 21. struct Operation { struct Init { … } struct Prepared { … } }
  22. 22. let operation = Operation.Init() let preparedOp = operation.prepared() preparedOp.execute()
  23. 23. class Operation { fileprivate var data: OperationData fileprivate init(data: OperationData) { self.data = data } func mob() {} }
  24. 24. extension Operation { class Init: Operation { … } class Prepared: Operation { … } }
  25. 25. let operation = Operation.Init() let preparedOp = operation.prepared() preparedOp.execute()
  26. 26. class HogeOperation<State: OperationState> { func mob() {…} } extension HogeOperation where State: Init { func prepared() -> HogeOperation<Prepared> {…} } extension HogeOperation where State: Prepared { func execute() {…} }
  27. 27. let operation = HogeOperation<Init>() operation.execute()
  28. 28. let operation = OperationInit() operation.execute()
  29. 29. // 準備前と準備後を、自分自身や同じ変数に書き戻せない var operation = HogeOperation<Init>() operation = operation.prepared()
  30. 30. protocol Operation {} class HogeOperation<State: OperationState>: Operation { } var op: Operation op = HogeOperation<Init>() op = (op as! HogeOperation<Init>).prepared() op = (op as! HogeOperation<Prepared>).execute()
  31. 31. class Driver { init(channel: Int? = nil, volume: Int? = nil, pan: Int? = nil, format: Format? = nil, route: Route? = nil) { } } // 設定項目に何があるかや、設定順番に気を使う let driver = Driver(volume: 10, format: ulaw, route: .speaker)
  32. 32. // 本体のクラスを Phantom Type で定義して… class Driver<State> where State: AudioState { } // 準備が整ったときの機能を実装し… extension Driver where State: Ready { func start() { … } }
  33. 33. // 初期化中にできることを規定すると… extension Driver where State: Setup { func prepared() -> Driver<Ready> { … } func set(channel: Int) -> Driver { return self } func set(volume: Int) -> Driver { return self } func set(pan: Int) -> Driver { return self } func set(format: Format) -> Driver { return self } func set(route: Route) -> Driver { return self } }
  34. 34. // 初期設定では、順番を気にせず設定できる・補完が効く let driver = Driver<Setup>() .set(format: ulaw) .set(volume: 10) .set(route: .speaker) .prepared() // ここで Driver<Ready>() を返す // 設定完了を宣言 (prepared) して、使い始める driver.start()
  35. 35. let driver = Driver<Setup>() .format // → Driver<FormatSetup> .set(sampleRate: 44100) .set(channelsPerFrame: 2) .general // → Driver<GeneralSetup> .set(volume: 10) .set(route: .speaker) .prepared() // → Driver<Ready>
  36. 36. let driver = Driver.setup() // → DriverSetup .format // → FormatSetup .set(sampleRate: 44100) .set(channelsPerFrame: 2) .general // → AudioSetup .set(volume: 10) .set(route: .speaker) .prepared() // → Driver
  37. 37. // 設定項目を、初期値を持った構造体で用意して… struct Description { var channel: Int = default var volume: Int = default var pan: Int = default var format: Format = default var route: Route = default }
  38. 38. let description = Description() description.format.sampleRate = 44100 description.format.channelsPerFrame = 2 description.volume = 10 description.route = .speaker
  39. 39. // Driver は Description で初期化するようにして… class Driver { init(description: Description) { … } } // 設定項目を渡して、初期化を完成する let driver = Driver(description: description)
  40. 40. class Controller { var environment: Environment<Neutral> }
  41. 41. struct Environment<State> { fileprivate(set) var platform: Platform fileprivate(set) var version: Double func startEditing() -> Environment<Editing> { return Environment<Editing>( platform: platform, version: version) } }
  42. 42. extension Environment where State: Editing { mutating func set(platform: Platform) { … } mutating func set(version: Double) { … } func endEditing() -> Environment<Neutral> { return Environment<Neutral>( platform: platform, version: version) } }
  43. 43. func update() { // 編集状態で取り出さないと、書き込めない var environment = self.environment.startEditing() environment.set(platform: .macOS) environment.set(version: Platform.macOS.latest) // ローカルで編集を終了したら、書き戻す self.environment = environment.endEditing() }
  44. 44. extension Environment where State: Editing { var platform: Platform var version: Double // 同じ内容の、別インスタンスを作り直している func endEditing() -> Environment<Neutral> { return Environment<Neutral>( platform: platform, version: version) }
  45. 45. extension Environment where State: Editing { // 内容を原始的にコピーしないといけないとき func endEditing() -> Environment<Neutral> { var result = Environment<Neutral>() result.platform = platform result.version = version return result }
  46. 46. struct Environment<State> { fileprivate(set) var platform: Platform fileprivate(set) var version: Double } extension Environment { fileprivate init<S>(takeover: Environment<S>) { platform = takeover.platform version = takeover.version } }
  47. 47. extension Environment where State: Editing { func endEditing() -> Environment<Neutral> { return Environment<Neutral>(takeover: self) } }
  48. 48. struct Environment<State> { // データをここで集中管理する fileprivate struct Context { var platform: Platform var version: Double } // これだけを引き継げば済む状況を作る fileprivate var _context: Context }
  49. 49. extension Environment { fileprivate init(context: Context) { _context = context } }
  50. 50. extension Environment where State: Editing { func endEditing() -> Environment<Neutral> { return Environment<Neutral>(context: _context) } }
  51. 51. extension Environment { var platform: Platform { get { _context.platform } set { _context.platform = newValue } } var version: Double { get { _context.version } set { _context.version = newValue } }
  52. 52. extension Environment where State: Editing { func endEditing() -> Environment<Neutral> { // 準備不要で、いきなりビットキャスト可能 return unsafeBitCast(self, to: Environment<Neutral>.self) } }
  53. 53. extension Environment { fileprivate init<S>(takeover: Environment<S>) { self = unsafeBitCast(takeover, to: Environment.self) } }
  54. 54. let sub: Base = Sub() let base: Base = Base() // 実体が Sub なので、全ての機能が使える let obj: Sub = unsafeBitCast(sub, to: Sub.self) // 実体が Base なので、Sub の機能を使うとクラッシュする let obj: Sub = unsafeBitCast(base, to: Sub.self)
  55. 55. class Test: OperationState {} extension HogeOperation where State: Test { func testSomething() {…} } extension HogeOperation where State: Init { func testing() -> HogeOperation<Test> {…} }
  56. 56. // Test 状態を Prepared から継承させれば… class Test: Prepared {} // Test には Prepared の機能も備わる extension HogeOperation where State: Prepared { func execute() {…} } extension HogeOperation where State: Test { func testSomething() {…} }

×