SwiftにはOptional型がありますが、エラーの時にOptional型を返すと、エラーの情報を返せないのでよくないと思っています。どんな実装でもいいのでEither的な型を使いましょう。
すでに様々な実装がありますが、私も作ってみました。まだインターフェイスを固定したくないのでCarthageやCocoaPodsには対応してませんが、デファクトなものがなければこれで行くつもりでいます。
使い方はREADME#SYNOPSISのとおりです。
Either Chaining
optional chainingに相当するchain()メソッドと、null coalescing operatorに相当するfallback()メソッドがあります。
成功状態を返すsuccess()と、失敗状態とエラー情報を返すfailure()があるとして、chain()は以下のように使います。
// 最初の値が成功状態であれば、chain()のブロックを実行してその値を返す
success("success").chain({ (m) -> Either<String, Error> in
return success("chained")
}) // success("chained")
// 最初の値が失敗状態であれば、最初の値をそのまま返す
// エラー情報をそのまま持っているので正しくエラーハンドリングが可能
failure("error!").chain({ (m) -> Either<String, Error> in
return Either(success: "chained")
}) // "error!"
Fallback Operation
null coalescing operatorに相当するのはfallback()メソッドです。エイリアスとして「??」演算子もあります。
// 最初の値が成功状態であれば、それをそのまま返す
success("success").fallback({
return success("chained")
}) // "success"
// 最初の値が失敗状態であれば、fallback()の引数ブロックを実行してその値を返す
// つまり、ある値が失敗したら次の演算にフォールバックする
failure("error!").fallback({
return success("fallback")
}) // "fallback"
// 演算子のほうが挙動はわかりやすいかも
failure("error!") ?? success("fallback") // success("fallback")
// 演算子の右辺値は生の値でもよい
failure("error!") ?? "fallback" // success("fallback")
Unwrapping
Optional型のようにoptional bindingはありませんが、switchによるパターンマッチで同じことができます。実際、optional bindingはパターンマッチの糖衣構文にすぎません。
switch success("foo") {
case .Success(let s):
// 成功状態の場合
// sは参照型のコンテナなので値を得るには.valueが必要
s.value // "foo"
case .Failure(let f):
// 失敗状態の場合
// f.valueはError型
f.value.reason // not reached
}
successValueとfailureValueがそれぞれOptional型を返すので、入っている値が確定しているならそれでもかまいません。
(success("foo").successValue)! == "foo" // true
(failure("foo").failureValue)! == "foo" // true
捕捉
Swiftにはタプルがあるので、エラーを返すときにgolangのように (T?, Error?)
というタプルを返すこともできます。しかし、これは戻り値をOptional型にせざるをえません。Optional型はエラー情報を持たないEither型とも考えられるため、それならば結局Either的なものを返すほうがシンプルでいいと思います。