protocol (extension)内でenumのパターンマッチをしたい。
簡単な例として、入力された数字が3の倍数かどうかを判定する関数を用いて処理を分岐することを考えます。
func foo(x: Int) -> Bool {
return x % 3 == 0
}
func main() {
switch foo(1) {
case true:
print("true")
case false:
print("false")
}
}
これを継続渡しスタイルで考えるとこんな感じになります。
func foo2<A>(x: Int, f: Bool -> A) -> A {
return f(x % 3 == 0)
}
func main() {
foo2(1){ x in
switch x {
case true:
print("true")
case false:
print("false")
}
}
}
foo(x: Int) -> Boolの場合、
fooに値を渡しBool値を得て、Bool)がtrueかfalseか判断し、print())という手順になります。一方、 継続渡しスタイルfoo2<A>(x: Int, f: Bool -> A) -> Aの場合は、
foo2に値と関数を渡し、foo2がBool値を関数に適用する。という手順になります。つまり継続渡しスタイルでは、関数の計算結果を判断するという部分を関数側に押しやってしまっているように見えます。
enumのパターンマッチの場合を考えます。つまり、enumをパターンマッチして何か行いたい場合において、パターンマッチ部分を関数側に押しやってしまうわけです。
具体例として、以下のようなEither<A, B>を考えます。
enum Either<L, R> {
case Left(L)
case Right(R)
}
Either<L, R>が準拠するprotocol EitehrTypeを定義し、その中でパターンマッチ用のメソッドeitherを定義します。
protocol EitherType {
typealias LeftType
typealias RightType
func either<A>(@noescape ifLeft ifLeft: LeftType -> A, @noescape ifRight: RightType -> A) -> A
}
extension Either: EitherType {
typealias LeftType = L
typealias RightType = R
func either<A>(@noescape ifLeft ifLeft: L -> A, @noescape ifRight: R -> A) -> A {
switch self {
case .Left(let x):
return ifLeft(x)
case .Right(let x):
return ifRight(x)
}
}
}
これによりextension EitherType内でパターンマッチが可能になります。
extension EitherType {
var right: RightType? {
return either(
ifLeft: { _ in nil },
ifRight: { $0 }
)
}
}
let a: Either<Int, Int> = .Left(0)
a.right // nil
let b: Either<Int, Int> = .Right(0)
b.right // 0
これだけだとEither側で実装すればいいんじゃ…と思ってしまうんですが、protocol内でパターンマッチできることでwhere文にEitherTypeが出てくる場合にもパターンマッチできるようになります。
extension Array where Element: EitherType {
func rights() -> [Element.RightType] {
return self
.map{ $0.right }
.filter{ $0 != nil }
.map{ $0! }
}
}
let eithers: [Either<Int, Int>] = [
.Left(0),
.Right(1),
.Left(2),
.Left(3),
.Right(4)
]
eithers.rights() // [1, 4]