はじめて phantom と遭遇して、闇雲に闘いを挑んでみた話 #kbkz_tech
TRANSCRIPT
class HogeOperation { private(set) var isPrepared = false func prepare() { guard !isPrepared else { fatalError("prepare を複数回呼ばないこと") }
isPrepared = true } }
// Init 状態の HogeOperation を生成 let operation = HogeOperation<Init>()
// 最初は prepared を呼べて準備完了したものを取得可能 let preparedOp = operation.prepared()
// 準備が終われば prepared はビルドエラーで実行不可 preparedOp.prepared()
// これで "操作状態を表現する型" を表現 protocol OperationState {}
// 操作状態ごとにクラスを定義 class Init: OperationState {} class Prepared: OperationState {}
// 型パラメーターで型に状態を付与 class HogeOperation<State: OperationState> {
/* 今回は内部で、 型パラメーターを使わないのがポイント */
}
extension HogeOperation where State: Init {
// 準備を実行し、準備完了状態のインスタンスを返す func prepared() -> HogeOperation<Prepared> {…} }
extension HogeOperation where State: Prepared {
// 目的の操作を実行する func execute() {…} }
let operation = HogeOperation<Init>()
// Init 状態では、まだ execute は存在しない operation.execute()
// prepared を呼ぶことで Prepared 状態のものを取得 let preparedOp = operation.prepared()
// Prepared 状態には、もう prepared は存在しない preparedOp.prepared()
let operation = HogeOperation<Init>()
// HogeOperation<Init> 型だから prepared が呼べる let preparedOp: HogeOperation<Prepared>() = operation.prepared()
// HogeOperation<Prepared> 型だから execute が呼べる preparedOp.execute()
// Init クラスや Prepared クラスを、実行時には使わない // ビルドの段階で、もう役目が済んでいる
let operation = HogeOperation<Init>() let preparedOp = operation.prepared()
let type1 = type(of: operation)
let type2 = type(of: preparedOp)
type1 == type2 // false
// 型パラメーターで型に状態を付与 class HogeOperation<State: OperationState> {
/* 内部では、型パラメーターを使っていない 型の在り方を説明するためだけに使っている */
/// Phantom Type に出逢う前の認識
// Array はテンプレート的なもので… struct Array<Element> { }
// 型パラメーターによって、異なる型になる let values: Array<Int> = Array<String>()
// protocol OperationState {}
// class Init: OperationState {} // class Prepared: OperationState {}
class Operation { fileprivate var data: OperationData fileprivate init(data: OperationData) { self.data = data } func mob() {} }
class OperationInit: Operation {
convenience init() {…} func prepared() -> OperationPrepared {…} }
class OperationPrepared: Operation {
func execute() {…} }
let operation = OperationInit() let preparedOp = operation.prepared()
preparedOp.execute()
struct OperationInit {
fileprivate var data: OperationData fileprivate init(data: OperationData) {…}
// 共通機能 func mob() {}
// 固有の機能 init() {…} func prepared() -> OperationPrepared {…} }
struct OperationPrepared {
fileprivate var data: OperationData fileprivate init(data: OperationData) {…}
// 共通機能 func mob() {}
// 固有の機能 func execute() {…} }
let operation = OperationInit() let preparedOp = operation.prepared()
preparedOp.execute()
protocol Operation {}
struct OperationInit: Operation {
… }
struct OperationPrepared: Operation {
… }
struct Operation {
struct Init {
… }
struct Prepared {
… } }
let operation = Operation.Init() let preparedOp = operation.prepared()
preparedOp.execute()
class Operation { fileprivate var data: OperationData fileprivate init(data: OperationData) { self.data = data } func mob() {} }
extension Operation {
class Init: Operation {
… }
class Prepared: Operation {
… } }
let operation = Operation.Init() let preparedOp = operation.prepared()
preparedOp.execute()
class HogeOperation<State: OperationState> { func mob() {…} }
extension HogeOperation where State: Init { func prepared() -> HogeOperation<Prepared> {…} }
extension HogeOperation where State: Prepared { func execute() {…} }
let operation = HogeOperation<Init>()
operation.execute()
let operation = OperationInit()
operation.execute()
// 準備前と準備後を、自分自身や同じ変数に書き戻せない var operation = HogeOperation<Init>() operation = operation.prepared()
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()
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)
// 本体のクラスを Phantom Type で定義して… class Driver<State> where State: AudioState {
}
// 準備が整ったときの機能を実装し… extension Driver where State: Ready {
func start() { … } }
// 初期化中にできることを規定すると… 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 } }
// 初期設定では、順番を気にせず設定できる・補完が効く let driver = Driver<Setup>() .set(format: ulaw) .set(volume: 10) .set(route: .speaker) .prepared() // ここで Driver<Ready>() を返す
// 設定完了を宣言 (prepared) して、使い始める driver.start()
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>
let driver = Driver.setup() //→ DriverSetup
.format //→ FormatSetup .set(sampleRate: 44100) .set(channelsPerFrame: 2)
.general //→ AudioSetup .set(volume: 10) .set(route: .speaker)
.prepared() //→ Driver
// 設定項目を、初期値を持った構造体で用意して… struct Description {
var channel: Int = default var volume: Int = default var pan: Int = default var format: Format = default var route: Route = default }
let description = Description()
description.format.sampleRate = 44100 description.format.channelsPerFrame = 2 description.volume = 10 description.route = .speaker
// Driver は Description で初期化するようにして… class Driver {
init(description: Description) { … }
}
// 設定項目を渡して、初期化を完成する let driver = Driver(description: description)
class Controller { var environment: Environment<Neutral> }
struct Environment<State> {
fileprivate(set) var platform: Platform fileprivate(set) var version: Double
func startEditing() -> Environment<Editing> {
return Environment<Editing>( platform: platform, version: version) } }
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) } }
func update() {
// 編集状態で取り出さないと、書き込めない var environment = self.environment.startEditing()
environment.set(platform: .macOS) environment.set(version: Platform.macOS.latest) // ローカルで編集を終了したら、書き戻す self.environment = environment.endEditing() }
extension Environment where State: Editing {
var platform: Platform var version: Double
// 同じ内容の、別インスタンスを作り直している func endEditing() -> Environment<Neutral> {
return Environment<Neutral>( platform: platform, version: version) }
extension Environment where State: Editing {
// 内容を原始的にコピーしないといけないとき func endEditing() -> Environment<Neutral> {
var result = Environment<Neutral>()
result.platform = platform result.version = version
return result }
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 } }
extension Environment where State: Editing {
func endEditing() -> Environment<Neutral> {
return Environment<Neutral>(takeover: self) }
}
struct Environment<State> {
// データをここで集中管理する fileprivate struct Context {
var platform: Platform var version: Double }
// これだけを引き継げば済む状況を作る fileprivate var _context: Context }
extension Environment {
fileprivate init(context: Context) {
_context = context } }
extension Environment where State: Editing {
func endEditing() -> Environment<Neutral> {
return Environment<Neutral>(context: _context) }
}
extension Environment {
var platform: Platform {
get { _context.platform } set { _context.platform = newValue } } var version: Double {
get { _context.version } set { _context.version = newValue } }
extension Environment where State: Editing {
func endEditing() -> Environment<Neutral> {
// 準備不要で、いきなりビットキャスト可能 return unsafeBitCast(self, to: Environment<Neutral>.self) }
}
extension Environment {
fileprivate init<S>(takeover: Environment<S>) {
self = unsafeBitCast(takeover, to: Environment.self) } }
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)
class Test: OperationState {}
extension HogeOperation where State: Test {
func testSomething() {…} }
extension HogeOperation where State: Init {
func testing() -> HogeOperation<Test> {…} }
// Test 状態を Prepared から継承させれば… class Test: Prepared {}
// Test には Prepared の機能も備わる extension HogeOperation where State: Prepared { func execute() {…} } extension HogeOperation where State: Test { func testSomething() {…} }