try! Swift: 型消去問題をオブジェクト指向+関数型ハイブリッド方式で解いてみる

[try! Swift: Type Erasureのユースケースを考えてみた話｜サイバーエージェント 公式エンジニアブログ](http://ameblo.jp/principia-ca/entry-12142313027.html)という記事を拝見した。

try! Swiftで話題になった型消去について解説されている。良く理解できていなかったのでとても参考になった。ありがとうございます。

ところで最近私は[Swiftにおけるオブジェクト指向・ジェネリクスによる実装と関数型的実装の比較とそのハイブリッド方式の実装の検討 - Qiita](http://qiita.com/rizumita/items/5d7b7edcf5e164ef0329)という投稿をした。このようなハイブリッド方式でも型消去が必要な問題を解決できないかを考えてみた。

元記事と同様に以下のDefaultConvertibleプロトコルに適合した型を保存するための仕組みを考える。

```swift
protocol DefaultConvertible {
    /// UDへの保存時に使うキー文字列
    static var key: String { get }
    
    /// UDから取得した値をデータ型に変換
    init?(_ object: AnyObject)
    
    /// UDへはAnyObjectで保存するのでAnyObjectに変換
    func serialize() -> AnyObject
}

struct CameraConfig: DefaultConvertible {
    
    enum Size: Int {
        case Large, Medium, Small
    }
    
    let saveToCameraRoll: Bool
    let size: Size
    
    static let key = "CameraConfig"
    
    init?(_ object: AnyObject) {
        guard let dict = object as? [String: AnyObject] else { return nil }
        self.saveToCameraRoll = dict["cameraRoll"] as? Bool ?? true
        
        if let rawSize = dict["size"] as? Int, let size = Size(rawValue: rawSize) {
            self.size = size
        } else {
            self.size = .Medium
        }
    }
    
    func serialize() -> AnyObject {
        return ["cameraRoll": saveToCameraRoll, "size": size.rawValue]
    }
}
```

## 機能の器を作る

まず型消去だが元記事では、PersistentStoreやInMemoryStoreはassociatedtypeに対応するtypealiasを持たないのでDefaultStoreTypeとして、つまり同じ型として保持できない。よってAnyStoreで包んで同じ型として利用できるようにするということのようである。

ハイブリッド方式ではまずプロトコルを用いず基本の型をジェネリクスを利用して作成する。

```swift
struct DefaultStore<D: DefaultConvertible>{
    typealias Default = D
    
    let set: Default -> ()
    let get: () -> Default?
    let remove: () -> ()
    
    init(set: Default -> (), get: () -> Default?, remove: () -> ()) {
        self.set = set
        self.get = get
        self.remove = remove
    }
}
```

このDefaultStoreは機能はいっさい持っておらず、機能の器となっている。

## 機能を作る

器に入れる機能を作成する。DefaultStoreを作成する時に注入するクロージャ3つを含むタプルを返す。DefaultsStoringはNSUserDefaultsを利用し、InMemoryStoringはDictionaryを利用する。

```swift
struct DefaultsStoring {
    static func storing<D: DefaultConvertible>() -> (set: D -> (), get: () -> D?, remove: () -> ()) {
        let defaults = NSUserDefaults.standardUserDefaults()
        return (
            { value in
                let obj = value.serialize()
                defaults.setObject(obj, forKey: D.key)
            },
            {
                guard let obj = defaults.objectForKey(D.key) else { return nil }
                return D(obj)
            },
            {
                defaults.removeObjectForKey(D.key)
            }
        )
    }
}

struct InMemoryStoring {
    static func storing<D: DefaultConvertible>() -> (set: D -> (), get: () -> D?, remove: () -> ()) {
        var dictionary: [String:AnyObject] = [:]
        return (
            { value in
                let obj = value.serialize()
                dictionary[D.key] = obj
            },
            {
                guard let obj = dictionary[D.key] else { return nil }
                return D(obj)
            },
            {
                dictionary[D.key] = nil
            }
        )
    }
}
```

## 機能を器に注入する

以下のように器であるDefaultStoreに機能を注入する。§はinitにstoring()の返値を適用するSwiftzで定義されている演算子で、Option+6で入力できる。

```swift
let defaultsStore = DefaultStore<CameraConfig>.init § DefaultsStoring.storing()
let inMemoryStore = DefaultStore<CameraConfig>.init § InMemoryStoring.storing()
```

これでdefaultsStoreもinMemoryStoreも同じDefaultStore<CameraConfig>という型なので、DefaultStore<CameraConfig>を利用する場面ではどちらでも扱うことが可能になる。

## 考察

ハイブリッド方式は器と機能を分けて定義することで器に柔軟性を持たせることができるようである。
オブジェクト指向・プロトコル・ジェネリクスを組み合わせて型消去が必要になる場面では、オブジェクト指向+関数型のハイブリッド方式で対応することもできるようである。ただし実用性は今後実際に利用していく中で検証していく必要がある。