O slideshow foi denunciado.
Utilizamos seu perfil e dados de atividades no LinkedIn para personalizar e exibir anúncios mais relevantes. Altere suas preferências de anúncios quando desejar.

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

2.133 visualizações

Publicada em

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

Publicada em: Software
  • Seja o primeiro a comentar

はじめて 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() {…} }

×