SwiftのPhantom TypeとLIO

# はじめに

Swiftの界隈では**Phantom Type（幽霊型）**という、型に状態をエンコードする手法がある。この記事ではまずこのPhantom Typeを用いた手法について解説し、この方法よりも著者がよいと思う`LIO`というデータ構造と、それを用いたコードを紹介する。なお、この記事で紹介したコードは下記のリポジトリに置かれている。

- https://github.com/y-yu/LIO-in-Swift


この記事を読んで疑問に思った点や改善するべき点を見つけた場合は、気軽にコメントなどで教えて欲しい。

# Phantom Typeと状態のエンコード

Phantom Type型とは、Haskellなどの界隈では歴史のある言葉であるが、Swiftでは次のような用途で用いられる[^vs_haskell]。次のコードは[Swift で Phantom Type (幽霊型)](http://qiita.com/taketo1024/items/71e3272211f08d7e0cde)より引用した。

[^vs_haskell]: このように書いたように、Haskellなどの文脈で用いられるPhantom TypeとSwiftの文脈で用いられるPhantom Typeの間には差があるように感じられるが、ここでは微妙な定義論は行わず両方ともPhantom Typeと呼ぶ。

```swift
class Status{}
class NotReady: Status{}
class Ready: Status{}

class Something<T: Status> {
    static func createInstance() -> Something<NotReady> {
        return Something<NotReady>()
    }

    func readify() -> Something<Ready> {
        return Something<Ready>()
    }
}

extension Something where T: Ready {
    func shout() {
        print("phantom types are awesome!")
    }
}
```

これは、クラス`Something`の型パラメータ`T`に状態を表す型を取り、`Something<NotReady>`であるインスタンスに対して`shout`を呼び出すとコンパイルエラーになるというものである。

```swift
let a = Something.createInstance()
a.shout() // コンパイルエラー！
```

このように、状態を型パラメータにエンコードして管理しようという試みである。

# SwiftにおけるPhantom Typeの課題

この実装には次のような課題があると思われる。

- `Something<NotReady>`と`Something<Ready>`のインスタンスを間違えたら、実行時エラーが発生する
  - この例では`NotReady`→`Ready`にしか遷移がないが、たとえば`Ready`→`NotReady`と遷移したにも関わらず`Ready`のインスタンスを用いれば恐らく実行時エラーとなる
- 状態が変化するたびにクラスのインスタンスを作成するので、負荷が高まる恐れがある

これらを解決するために、より汎用的なデータ構造`LIO`を考える。

# データ構造`LIO`

`LIO`は次のようなデータ構造である。

```swift:LIO.swift
struct LIO<P, Q, A> {
    let run: () -> A
    
    init(_ r: @escaping () -> A) {
        self.run = r
    }
    
    init(_ a: A) {
        self.run = { () -> A in return a }
    }
    
    func flatMap<B, R>(_ f: @escaping (A) -> LIO<Q, R, B>) -> LIO<P, R, B> {
        return LIO<P, R, B> {
            () -> B in f(self.run()).run()
        }
    }
    
    func map<B>(_ f: @escaping (A) -> B) -> LIO<P, Q, B> {
        return LIO<P, Q, B> {
            () -> B in f(self.run())
        }
    }
}
```

`LIO`は3つの型パラメータを持つ。型パラメータ`P`は`run`を実行する前の状態を表す型であり、型パラメータ`Q`は`run`を実行した後の状態を表す型である。また、最後の型パラメータ`A`は`run`の結果の型である。
`flatMap`と`map`は、ある二つの`LIO`を合成するための関数である。これらの関数がどういった役に立つかは、次の利用例で明らかになる。

# `LIO`の利用例

いま、ファイルのようにロック状態と非ロック状態があるものを考える。ロック状態であれば書き込みや読み込みができ、そうでなければ読み書きはできないとする。
まず、ロックと非ロックの二つの状態を表わす適当なデータ型を作成する。

```swift:State.swift
struct Lock {
    init() {}
}

struct Unlock {
    init() {}
}
```

そして、ロックを行う関数とロックを解除する関数を用意する。この関数はそれぞれ次のように状態を変更する。

- ロックを行う関数は非ロック状態でのみ実行でき、実行すると状態がロック状態に変化する
- ロックを解除する関数はロック状態でのみ実行でき、実行すると状態が非ロック状態に変化する

これを実装する次のようになる。

```swift:main.swift
func lock() -> LIO<Unlock, Lock, Void> {
    // 複雑なロック処理があるものとする
    return LIO()
}

func unlock() -> LIO<Lock, Unlock, Void> {
    // 複雑なロック解除処理があるものとする
    return LIO()
}
```

このように、`LIO<P, Q, A>`は状態`P`から`Q`へ変化すると考えることができるので、このようになる。同じように読み込みと書き込みが行えるものとする。

```swift:main.swift
func put(_ str: String) -> LIO<Lock, Lock, Void> {
    // 標準出力に表示する
    return LIO(print(str))
}

func get() -> LIO<Lock, Lock, String> {
    // 標準入力から受け取る
    return LIO(readLine()!)
}
```

これら`put`と`get`はロック状態のときのみに実行でき、実行してもロック状態に変化がないので、このようになる。
そして、これらを次のように合成して使うことができる。

```swift:main.swift
let a = lock().flatMap { _ in
        put("piyo").flatMap { _ in
        put("mofu").flatMap { _ in
        get().flatMap { input in
        put(input).flatMap { _ in
        unlock().map { _ in return }}}}}}

let _ = a.run()
```

これの結果は次のようになる[^input]。

```
piyo
mofu
> this is an input
this is an input
```

[^input]: ただし、`>`が付いている行は標準入力で与えた文字列である。

たとえば次のようなコードがコンパイルエラーとなる。

```swift
// 二重にロックしようとした
let ng1 = lock().flatMap { _ in
          lock().map { _ in return }}

// ロックを解除した後に書き込んだ
let ng2 = unlock().flatMap { _ in
          put("piyo").map { _ in return }}
```

これで一見よさそうに見える。

# さらなる改良

これで一応できることはできるが、`flatMap`が長いので専用の演算子を定義して短くする。

```swift:LIO.swift
infix operator >>=

func >>=<P, Q, R, A, B>(_ ma: LIO<P, Q, A>, _ f: @escaping (A) -> LIO<Q, R, B>) -> LIO<P, R, B> {
    return ma.flatMap(f)
}
```
二項演算子`>>=`は`flatMap`を呼び出すだけである。このような演算子を用意することで、次のように書ける。

```swift:main.swift
let b = lock() >>= { _ in
        put("piyo") >>= { _ in
        put("mofu") >>= { _ in
        get() >>= { input in
        put(input) >>= { _ in
        unlock().map { _ in return }}}}}}

let _ = b.run()
```

ただしこれでもやや表記が多い。よく見ると`put`を使う部分は`LIO<P, Q, A>`の`A`が`Void`であるので引数を使っておらず、次の`put`や`get`を直ちに起動している。そこで、次のような二項演算子も定義する。

```swift:LIO.swift
precedencegroup Right {
    associativity: right
}

infix operator >>>: Right

func >>><P, Q, R, A, B>(_ ma: LIO<P, Q, A>, _ mb: LIO<Q, R, B>) -> LIO<P, R, B> {
    return ma >>= { _ in return mb }
}
```

これを用いると、次のように書ける。

```swift:main.swift
let c = lock() >>>
        put("piyo") >>>
        put("mofu") >>>
        get() >>= { input in
            put(input) >>> unlock()
        }

let _ = c.run()
```

この結果は先ほどと同じように次のようになる。

```
piyo
mofu
> this is an input
this is an input
```

# Phantom Type _vs_ `LIO` 

既存のPhantom Typeを用いた手法は、次のような二つの課題があった。

> - `Something<NotReady>`と`Something<Ready>`のインスタンスを間違えたら、実行時エラーが発生する
> - 状態が変化するたびにクラスのインスタンスを作成するので、負荷が高まる恐れがある

まず、最初の課題については`LIO`のインスタンスに対しての操作は`flatMap`または`map`、および最後の`run`しかなく、特別なインスタンスを作成しない。よって、インスタンスを間違えたりすることはないと考えられる。
また、後者については、`LIO`は非常にシンプルなデータ構造であるのでインスタンスの作成コストは複雑なクラスと比べて少ないと考えられる。ただし、`flatMap`の際に`run`をクロージャーで拡張していくので、Phantom Typeを用いた手法と比べて効率が確実に良いかどうかは分からない。これについてはベンチマークなどで検証する必要があると考えられる。

# まとめ

このように、`LIO`というデータ構造を用いて既存のPhantom Typeを用いた状態の管理に比べてよさそうな方法を提案した。Phantom Typeと呼ばれる手法に比べて全てがよいかどうかはまだ分からないが、ひとつの方法として考えてもよいかもしれない。
この手法はもともとHaskellの世界で生れたものなので、Swiftの世界にHaskellで生れたものを持っていけたらよいかもしれない。

# 参考文献

- [Swift で Phantom Type (幽霊型)](http://qiita.com/taketo1024/items/71e3272211f08d7e0cde)
- [はじめて Phantom と遭遇して、闇雲に闘いを挑んでみた話](http://www.slideshare.net/tomohirokumagai54/phantom-kbkztech)
- [Parameterized `monad'](http://okmij.org/ftp/Computation/monads.html#param-monad)
- [VarStateM.hs](http://okmij.org/ftp/Computation/VarStateM.hs)