(2015.09.08 追記) Swift 2.0 ではこんなことをしなくても Either
を作ることができるようになっています。 Either
について詳しくはこちら。
(2014.12.13 追記) ここに書いた方法は遅延評価の影響でクロージャにキャプチャされた変数が変更された時などに問題がありました。思い付きで書くと良くないですね。イミュータブルな世界なら問題ないのですが。
僕の思い付くミュータブルな世界でも使えるワークアラウンドは↓みたいな感じです。
enum Either<T, U> {
case Left(Container<T>)
case Right(Container<U>)
static func left(left:T) -> Either<T, U> {
return .Left(Container<T>(left))
}
static func right(right:U) -> Either<T, U> {
return .Right(Container<U>(right))
}
}
class Container<T> {
let value: T
init(_ value: T) {
self.value = value
}
}
let either1 = Either<Int, String>.left(123)
let either2 = Either<Int, String>.right("abc")
switch either1 {
case .Left(let integer):
println(integer.value)
case .Right(let string):
println(string.value)
}
Swift の enum
は Associated Value を使って Tagged Union のようなことができるので、次のような型を作りたくなります。
enum Either<T, U> {
case Left(T)
case Right(U)
}
しかし、 Swift 1.1 時点では次のようなコンパイルエラーになってしまいます。
Unimplemented IR generation feature non-fixed multi-payload enum layout
これを回避して同じようなことをやるには @autoclosure
を使うことができます。クロージャに包めば参照型になるのでサイズが固定されます。 Java でプリミティブ型をボクシングするのと似てます。
enum Either<T, U> {
case Left(@autoclosure () -> T)
case Right(@autoclosure () -> U)
}
@autoclosure
は値を渡せば勝手にクロージャに変換してくれる機能です。上記の Either
はクロージャの存在を気にせず次のように使えます。
let either1 = Either<Int, String>.Left(123)
let either2 = Either<Int, String>.Right("abc")
ただし、値にアクセスするときはコールして戻り値を得なければなりません。
switch either1 {
case .Left(let integer):
println(integer()) // ここでコールのための () が必要
case .Right(let string):
println(string()) // ここでコールのための () が必要
}
@autoclosure
の逆のような ()
なしでアクセスするとコールした戻り値が得られる Attribute があればいいんですけどね(その場合どうやって関数オブジェクトをとればいいんだという話はありますが。でもそれって Property や Subscript も同じ?)。
@autoclosure
を使うのは有名な話なのかもしれませんが、ちょっと思い付いたので小ネタとして書いてみました。