View
266
Download
1
Category
Preview:
Citation preview
Phantom Typeでコンパイル時に 状態チェックをする
shibuya.swift #4
Kazuhiro Hayashi
Phantom Typeとは...
要は型パラメータを使って、 見た目上現れない制約を付けることらしい(?)
Phantom Typeとは...
幽霊みたいな型
要は型パラメータを使って、 見た目上現れない制約を付けることらしい(?)
Phantom Typeとは...
幽霊みたいな型
Phantom Type
要は型パラメータを使って、 見た目上現れない制約を付けることらしい(?)
例えば…
Phantom Typeを使った クラス定義
class PokemonStateType {}
class Struggle: PokemonStateType {} // 戦闘状態 class Calm: PokemonStateType {} // 休憩状態
class Pokemon<T: PokemonStateType> { static func use() -> Pokemon<Calm> { return Pokemon<Calm>() } func ready() -> Pokemon<Struggle> { return Pokemon<Struggle>() } }
extension Pokemon where T: Struggle { func attack() {} }
Phantom Typeを使った クラス定義
class PokemonStateType {}
class Struggle: PokemonStateType {} // 戦闘状態 class Calm: PokemonStateType {} // 休憩状態
class Pokemon<T: PokemonStateType> { static func use() -> Pokemon<Calm> { return Pokemon<Calm>() } func ready() -> Pokemon<Struggle> { return Pokemon<Struggle>() } }
extension Pokemon where T: Struggle { func attack() {} }
状態を型パラメータとして持つポケモンクラスを作成する
Phantom Typeを使った クラス定義
class PokemonStateType {}
class Struggle: PokemonStateType {} // 戦闘可能状態 class Calm: PokemonStateType {} // 戦闘不可状態
class Pokemon<T: PokemonStateType> { static func use() -> Pokemon<Calm> { return Pokemon<Calm>() } func ready() -> Pokemon<Struggle> { return Pokemon<Struggle>() } }
extension Pokemon where T: Struggle { func attack() {} }
ポケモンは「戦闘状態」と「休憩状態」を持つ
class PokemonStateType {}
class Struggle: PokemonStateType {} // 戦闘状態 class Calm: PokemonStateType {} // 休憩状態
class Pokemon<T: PokemonStateType> { static func use() -> Pokemon<Calm> { return Pokemon<Calm>() } func ready() -> Pokemon<Struggle> { return Pokemon<Struggle>() } }
extension Pokemon where T: Struggle { func attack() {} }
Phantom Typeを使った クラス定義
class PokemonStateType {}
class Struggle: PokemonStateType {} // 戦闘可能状態 class Calm: PokemonStateType {} // 戦闘不可状態
class Pokemon<T: PokemonStateType> { static func use() -> Pokemon<Calm> { return Pokemon<Calm>() } func ready() -> Pokemon<Struggle> { return Pokemon<Struggle>() } }
extension Pokemon where T: Struggle { func attack() {} }
戦闘状態の時はattack()が実行可能
class PokemonStateType {}
class Struggle: PokemonStateType {} // 戦闘状態 class Calm: PokemonStateType {} // 休憩状態
class Pokemon<T: PokemonStateType> { static func use() -> Pokemon<Calm> { return Pokemon<Calm>() } func ready() -> Pokemon<Struggle> { return Pokemon<Struggle>() } }
extension Pokemon where T: Struggle { func attack() {} }
Pokemonクラスを使ってみる
let pokemon = Pokemon.use() pokemon.attack() // -> コンパイル時にエラー pokemon.ready().attack() // -> こっちはコンパイルエラーが出ない
Pokemonクラスを使ってみる
let pokemon = Pokemon.create() pokemon.attack() // -> コンパイル時にエラー pokemon.ready().attack() // -> こっちはコンパイルエラーが出ない
型パラメータにCalmが持つ場合、attack()メソッドは呼べない
let pokemon = Pokemon.use() pokemon.attack() // -> コンパイル時にエラー pokemon.ready().attack() // -> こっちはコンパイルエラーが出ない
Pokemonクラスを使ってみる
let pokemon = Pokemon.create() pokemon.attack() // -> コンパイル時にエラー pokemon.ready().attack() // -> こっちはコンパイルエラーが出ない
型パラメータにStruggleを持つPokemonの場合、attack()メソッドが呼べる
let pokemon = Pokemon.use() pokemon.attack() // -> コンパイル時にエラー pokemon.ready().attack() // -> こっちはコンパイルエラーが出ない
見かけ上は隠された型を使って 状態管理ができた…
実用例
https://github.com/kazuhiro4949/StringStylizer
StringStylizer
• NSAttributedStringを型によって使いやすくしたライブラリを作りました
• Phantom Typeによって範囲選択中とスタイル適用中の状態を切り替える
StringStylizerlet msg = “shibuya.swift".stylize() .range(0..<7) .color(0x009911).font(.HelveticaNeue) .range(8..<UInt.max).color(0xaa22cc).font(.HelveticaNeue_Bold).attr
• テキストに対してメソッドチェーンでスタイルを適用していく
• NSAttributedStringと同様、任意の範囲に対してスタイルを適用するという流れでつくる
状態遷移図としてまとめると
型パラメータを切り替えることで状態を行ったり来たりしながら、適切なメソッドをコールする
状態によって コンパイルエラーが起きる
// 範囲選択したあとで何かスタイルを適用していないとコンパイルエラー label.attributedText = "shibuya.swift".stylize().range(0..<7).attr
// 範囲選択後に色を適用しているのでコンパイルに通る
label.attributedText = "shibuya.swift".stylize().range(0..<7).color(0x009911).attr
例えばUILabelに対して、文字列のある範囲のみスタイルを適用して代入する場合を考える
状態によって コンパイルエラーが起きる
// 範囲選択したあとで何かスタイルを適用していないとコンパイルエラー label.attributedText = "shibuya.swift".stylize().range(0..<7).attr
// 範囲選択後に色を適用しているのでコンパイルに通る
label.attributedText = "shibuya.swift".stylize().range(0..<7).color(0x009911).attr
範囲選択をしている時は、その後で必ずスタイルの適用をする必要がある
状態によって コンパイルエラーが起きる
// 範囲選択したあとで何かスタイルを適用していないとコンパイルエラー label.attributedText = "shibuya.swift".stylize().range(0..<7).attr
// 範囲選択後に色を適用しているのでコンパイルに通る
label.attributedText = "shibuya.swift".stylize().range(0..<7).color(0x009911).attr
スタイルを適用した後は、別のスタイルを適用するかNSAttributedStringとして代入できる
Phantom Type まとめ
• 型パラメータによってオブジェクトの振る舞いに制約を与えられる
• コンパイラによって正しい振る舞いをしているかどうかチェックできる
• 間違ってたらごめん
もし何かうまい使い方があれば 教えて下さいm(_ _)m
参考資料• Swift で Phantom Type (幽霊型)
• http://qiita.com/taketo1024/items/71e3272211f08d7e0cde
• Functional Snippet #13: Phantom Types
• https://www.objc.io/blog/2014/12/29/functional-snippet-13-phantom-types/
• kazuhiro4949/StringStylizer
• https://github.com/kazuhiro4949/StringStylizer
Recommended