列挙型と型引数を用いる際の注意点
列挙型と型引数を用いる際、2つの型引数を用いた宣言や、再帰的なデータ型を型引数を用いた宣言を、次のように行いたいことがあります。
enum Result<T,S> {
case Success(T)
case Failure(S)
}
enum List<T> {
case Nil
case Cons(T, List<T>)
}
2015年2月現在、Swiftでは列挙型に2つの型引数を用いたり、再帰的なデータ型の定義のために列挙型のメンバー付随型に自分自身の型を用いたりすると、コンパイルエラーが起こります。
ここでのResult<T, S>型は、「成功した場合と失敗した場合を示す2つのメンバーを持った結果」を表す列挙型として使用できます。また、再帰的リスト型は、「続きの値がない場合とある場合を示す2つのメンバーを持ったリスト」を表す列挙型として使用できます。
このような列挙型の実現のために、SwiftzやBrightFuturesなどのオープンソースソフトウェアは、次のようなBox<T>クラスを付随型宣言の間にかませることでエラーを解消しています。
final class Box<T> {
let value: T
init(_ value: T) {
self.value = value
}
}
enum Result<T, S>: Printable {
case Success(Box<T>)
case Failure(Box<S>)
var description: String {
switch self {
case .Success(let val) : return "Success(\(val.value))"
case .Failure(let val) : return "Failure(\(val.value))"
}
}
}
enum List<T>: Printable {
case Nil
case Cons(Box<T>, Box<List<T>>)
var description: String {
switch self {
case .Nil : return "[]"
case .Cons(let val, let cons) : return "[\(val.value), \(cons.value.description)]"
}
}
}
let success = Result<Int, String>.Success(Box(1))
success.description // Success(1)
let failure = Result<Int, String>.Failure(Box("failed"))
failure.description // Failure(failed)
let list: List<Int> = List.Cons(Box(1), Box(List.Cons(Box(2), Box(List.Nil))))
list.description // [1, [2, []]]
この改善例では、ジェネリックタイプの型引数をメンバー付随型の中で使う際に必ずBox<T>クラスを挟んでいます。このような改善が必要な理由は不明です。「Swiftのコンパイラが、再帰的列挙型などの内部的なデータ長を予測できないから」といった憶測がありますが、詳細はAppleの開発者のみぞ知るといったところでしょうか。
ジェネリックタイプに対するエクステンション
エクステンションの中では、拡張元のジェネリックタイプに宣言された型引数を自由に使えます。このとき、元の型引数の宣言をする必要はありません。
extension ジェネリックタイプ名 {
ジェネリックタイプに宣言された型引数を宣言なしに用いてよい
}
先ほどのQueue<T>ストラクチャにエクステンションを用いて確認してみましょう。
struct Queue<T> {
private var values = [T]()
mutating func put(value: T) {
values.append(value)
}
mutating func get() -> T? {
let value = values.first
if values.count > 0 { values.removeAtIndex(0) }
return value
}
}
extension Queue {
subscript(index: UInt) -> T? {
let idx = Int(index)
return idx < values.count ? values[idx] : nil
}
}
var queue = Queue<String>()
queue.put("aa")
queue.put("456")
queue[0] // aa
queue.get() // aa
queue[1] // nil
Queue<T>に対して新しく宣言するエクステンションで、型引数を宣言することなくQueue本体に定義された型引数Tが使えていることに注目してください。ここではUInt型を用いたsubscriptでQueueの内部にアクセスをかけ、T?型を返すように宣言しています。indexがQueueの長さに達していなければOptional.Noneを返すようにしています。
次回は
今回はジェネリクスを中心に解説しました。型引数とプロトコルによる制約を中心としたプログラミングスタイルはSwiftのAPIでも使われている箇所が多いため、ぜひとも押さえておきたいところです。次回はいよいよ最終回。メタタイプと演算子の説明を中心に行います。お楽しみに。
