95

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

Embed Size (px)

Citation preview

Page 1: はじめて Phantom と遭遇して、闇雲に闘いを挑んでみた話 #kbkz_tech
Page 2: はじめて Phantom と遭遇して、闇雲に闘いを挑んでみた話 #kbkz_tech
Page 4: はじめて Phantom と遭遇して、闇雲に闘いを挑んでみた話 #kbkz_tech
Page 5: はじめて Phantom と遭遇して、闇雲に闘いを挑んでみた話 #kbkz_tech
Page 6: はじめて Phantom と遭遇して、闇雲に闘いを挑んでみた話 #kbkz_tech
Page 7: はじめて Phantom と遭遇して、闇雲に闘いを挑んでみた話 #kbkz_tech
Page 8: はじめて Phantom と遭遇して、闇雲に闘いを挑んでみた話 #kbkz_tech
Page 9: はじめて Phantom と遭遇して、闇雲に闘いを挑んでみた話 #kbkz_tech

class HogeOperation { private(set) var isPrepared = false func prepare() { guard !isPrepared else { fatalError("prepare を複数回呼ばないこと") }

isPrepared = true } }

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

// Init 状態の HogeOperation を生成 let operation = HogeOperation<Init>()

// 最初は prepared を呼べて準備完了したものを取得可能 let preparedOp = operation.prepared()

// 準備が終われば prepared はビルドエラーで実行不可 preparedOp.prepared()

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

// これで "操作状態を表現する型" を表現 protocol OperationState {}

// 操作状態ごとにクラスを定義 class Init: OperationState {} class Prepared: OperationState {}

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

// 型パラメーターで型に状態を付与 class HogeOperation<State: OperationState> {

/* 今回は内部で、 型パラメーターを使わないのがポイント */

}

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

extension HogeOperation where State: Init {

// 準備を実行し、準備完了状態のインスタンスを返す func prepared() -> HogeOperation<Prepared> {…} }

extension HogeOperation where State: Prepared {

// 目的の操作を実行する func execute() {…} }

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

let operation = HogeOperation<Init>()

// Init 状態では、まだ execute は存在しない operation.execute()

// prepared を呼ぶことで Prepared 状態のものを取得 let preparedOp = operation.prepared()

// Prepared 状態には、もう prepared は存在しない preparedOp.prepared()

Page 17: はじめて Phantom と遭遇して、闇雲に闘いを挑んでみた話 #kbkz_tech
Page 18: はじめて Phantom と遭遇して、闇雲に闘いを挑んでみた話 #kbkz_tech
Page 19: はじめて Phantom と遭遇して、闇雲に闘いを挑んでみた話 #kbkz_tech
Page 20: はじめて Phantom と遭遇して、闇雲に闘いを挑んでみた話 #kbkz_tech
Page 21: はじめて Phantom と遭遇して、闇雲に闘いを挑んでみた話 #kbkz_tech
Page 22: はじめて Phantom と遭遇して、闇雲に闘いを挑んでみた話 #kbkz_tech

let operation = HogeOperation<Init>()

// HogeOperation<Init> 型だから prepared が呼べる let preparedOp: HogeOperation<Prepared>() = operation.prepared()

// HogeOperation<Prepared> 型だから execute が呼べる preparedOp.execute()

// Init クラスや Prepared クラスを、実行時には使わない // ビルドの段階で、もう役目が済んでいる

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

let operation = HogeOperation<Init>() let preparedOp = operation.prepared()

let type1 = type(of: operation)

let type2 = type(of: preparedOp)

type1 == type2 // false

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

// 型パラメーターで型に状態を付与 class HogeOperation<State: OperationState> {

/* 内部では、型パラメーターを使っていない 型の在り方を説明するためだけに使っている */

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

/// Phantom Type に出逢う前の認識

// Array はテンプレート的なもので… struct Array<Element> { }

// 型パラメーターによって、異なる型になる let values: Array<Int> = Array<String>()

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

// protocol OperationState {}

// class Init: OperationState {} // class Prepared: OperationState {}

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

class Operation { fileprivate var data: OperationData fileprivate init(data: OperationData) { self.data = data } func mob() {} }

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

class OperationInit: Operation {

convenience init() {…} func prepared() -> OperationPrepared {…} }

class OperationPrepared: Operation {

func execute() {…} }

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

let operation = OperationInit() let preparedOp = operation.prepared()

preparedOp.execute()

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

struct OperationInit {

fileprivate var data: OperationData fileprivate init(data: OperationData) {…}

// 共通機能 func mob() {}

// 固有の機能 init() {…} func prepared() -> OperationPrepared {…} }

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

struct OperationPrepared {

fileprivate var data: OperationData fileprivate init(data: OperationData) {…}

// 共通機能 func mob() {}

// 固有の機能 func execute() {…} }

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

let operation = OperationInit() let preparedOp = operation.prepared()

preparedOp.execute()

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

protocol Operation {}

struct OperationInit: Operation {

… }

struct OperationPrepared: Operation {

… }

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

struct Operation {

struct Init {

… }

struct Prepared {

… } }

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

let operation = Operation.Init() let preparedOp = operation.prepared()

preparedOp.execute()

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

class Operation { fileprivate var data: OperationData fileprivate init(data: OperationData) { self.data = data } func mob() {} }

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

extension Operation {

class Init: Operation {

… }

class Prepared: Operation {

… } }

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

let operation = Operation.Init() let preparedOp = operation.prepared()

preparedOp.execute()

Page 45: はじめて Phantom と遭遇して、闇雲に闘いを挑んでみた話 #kbkz_tech
Page 46: はじめて Phantom と遭遇して、闇雲に闘いを挑んでみた話 #kbkz_tech
Page 47: はじめて Phantom と遭遇して、闇雲に闘いを挑んでみた話 #kbkz_tech
Page 48: はじめて Phantom と遭遇して、闇雲に闘いを挑んでみた話 #kbkz_tech
Page 49: はじめて Phantom と遭遇して、闇雲に闘いを挑んでみた話 #kbkz_tech

class HogeOperation<State: OperationState> { func mob() {…} }

extension HogeOperation where State: Init { func prepared() -> HogeOperation<Prepared> {…} }

extension HogeOperation where State: Prepared { func execute() {…} }

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

let operation = HogeOperation<Init>()

operation.execute()

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

let operation = OperationInit()

operation.execute()

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

// 準備前と準備後を、自分自身や同じ変数に書き戻せない var operation = HogeOperation<Init>() operation = operation.prepared()

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

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()

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

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)

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

// 本体のクラスを Phantom Type で定義して… class Driver<State> where State: AudioState {

}

// 準備が整ったときの機能を実装し… extension Driver where State: Ready {

func start() { … } }

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

// 初期化中にできることを規定すると… 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 } }

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

// 初期設定では、順番を気にせず設定できる・補完が効く let driver = Driver<Setup>() .set(format: ulaw) .set(volume: 10) .set(route: .speaker) .prepared() // ここで Driver<Ready>() を返す

// 設定完了を宣言 (prepared) して、使い始める driver.start()

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

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>

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

let driver = Driver.setup() //→ DriverSetup

.format //→ FormatSetup .set(sampleRate: 44100) .set(channelsPerFrame: 2)

.general //→ AudioSetup .set(volume: 10) .set(route: .speaker)

.prepared() //→ Driver

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

// 設定項目を、初期値を持った構造体で用意して… struct Description {

var channel: Int = default var volume: Int = default var pan: Int = default var format: Format = default var route: Route = default }

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

let description = Description()

description.format.sampleRate = 44100 description.format.channelsPerFrame = 2 description.volume = 10 description.route = .speaker

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

// Driver は Description で初期化するようにして… class Driver {

init(description: Description) { … }

}

// 設定項目を渡して、初期化を完成する let driver = Driver(description: description)

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

class Controller { var environment: Environment<Neutral> }

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

struct Environment<State> {

fileprivate(set) var platform: Platform fileprivate(set) var version: Double

func startEditing() -> Environment<Editing> {

return Environment<Editing>( platform: platform, version: version) } }

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

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) } }

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

func update() {

// 編集状態で取り出さないと、書き込めない var environment = self.environment.startEditing()

environment.set(platform: .macOS) environment.set(version: Platform.macOS.latest) // ローカルで編集を終了したら、書き戻す self.environment = environment.endEditing() }

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

extension Environment where State: Editing {

var platform: Platform var version: Double

// 同じ内容の、別インスタンスを作り直している func endEditing() -> Environment<Neutral> {

return Environment<Neutral>( platform: platform, version: version) }

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

extension Environment where State: Editing {

// 内容を原始的にコピーしないといけないとき func endEditing() -> Environment<Neutral> {

var result = Environment<Neutral>()

result.platform = platform result.version = version

return result }

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

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 } }

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

extension Environment where State: Editing {

func endEditing() -> Environment<Neutral> {

return Environment<Neutral>(takeover: self) }

}

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

struct Environment<State> {

// データをここで集中管理する fileprivate struct Context {

var platform: Platform var version: Double }

// これだけを引き継げば済む状況を作る fileprivate var _context: Context }

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

extension Environment {

fileprivate init(context: Context) {

_context = context } }

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

extension Environment where State: Editing {

func endEditing() -> Environment<Neutral> {

return Environment<Neutral>(context: _context) }

}

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

extension Environment {

var platform: Platform {

get { _context.platform } set { _context.platform = newValue } } var version: Double {

get { _context.version } set { _context.version = newValue } }

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

extension Environment where State: Editing {

func endEditing() -> Environment<Neutral> {

// 準備不要で、いきなりビットキャスト可能 return unsafeBitCast(self, to: Environment<Neutral>.self) }

}

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

extension Environment {

fileprivate init<S>(takeover: Environment<S>) {

self = unsafeBitCast(takeover, to: Environment.self) } }

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

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)

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

class Test: OperationState {}

extension HogeOperation where State: Test {

func testSomething() {…} }

extension HogeOperation where State: Init {

func testing() -> HogeOperation<Test> {…} }

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

// Test 状態を Prepared から継承させれば… class Test: Prepared {}

// Test には Prepared の機能も備わる extension HogeOperation where State: Prepared { func execute() {…} } extension HogeOperation where State: Test { func testSomething() {…} }

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