Result
Result はOptionalに似通った型です。
Optionalは値が存在しているかいないかを表現できる型ですが、Result は値あるいは値がない理由を表現できる型です。
このResultをもっと積極的に使おうというのが趣旨です。
対応Swiftのバージョン: Swift 5.1 です。5.0でも動きません。
なお、書いた人がメソッドチェーン原理主義者の模様。
ifSuccess / ifFailure
名前の通り成功した時あるいは失敗した時のみに実行される関数を設定します。
extension Result {
@discardableResult
func ifSuccess(_ f: (Success) -> Void)-> Self {
if case let .success(v) = self { f(v) }
return self
}
@discardableResult
func ifFailure(_ f: (Failure) -> Void) -> Self {
if case let .failure(e) = self { f(e) }
return self
}
}
Selfを返しているのはメソッドチェーンをうまく使うためです。
@discardableresultを付けることで戻り値を利用しなくても警告が出ないようにしています。
利用例
struct AError: Error {}
Reuslt<Int, AError>(success: 0)
.ifSuccess { print("Success", $0) }
.ifFailure { print("Failure", $0) }
// prints Success 0
zip
extension Result {
func zip<T>(_ other: Result<T, Failure>) -> Result<(Success, T), Failure> {
flatMap { v1 in other.map { v2 in (v1, v2) } }
}
}
使用例
func getID() -> Result<String, AError> {
.success("Identifier")
}
func getPassword() -> Result<String, AError> {
.success("PASSWORD")
}
func authrize(id: String, password: String) -> Result<(), AError> {
.success(())
}
getID()
.zip(getPassword())
.flatMap(authrize)
.ifSuccess { _ in print("OK") }
.ifFailure { _ in print("NG") }
必要に応じてzip3
やzip4
を用意するのも良いでしょう。
sequence
[Reuslt<T, E>]
をResult<[T], E>
に変換する。
extension Sequence {
func sequence<T, E>() -> Result<[T], E>
where Element == Result<T, E> {
Result<[T], Error> { try map { try $0.get() } }
.mapError { $0 as! E }
}
}
mapError
内で force cast を行っていますが、try map { try $0.get() }
この部分が投げるErrorはE
ですので問題ありません。
使用例
extension Array where Element == Int {
func maxOrThrow() throws -> Int {
guard let v = self.max() else { throw AError() }
return v
}
}
[Result<Int, AError>.success(0), .success(2), .success(3)]
.sequence()
.flatMap { a in
Result<Int, Error> { try a.maxOrThrow() }
.mapError { $0 as! AError }
}
.ifSuccess { print($0) }
.ifFailure { print("Error", $0) }
// prints 3
traverse
Array
の各要素にResult<T, E>
を返す関数を適用してResult<[T], E>
に変換する。
extension Sequence {
func traverse<T, E>(_ f: (Element) -> Result<T, E>) -> Result<[T], E> {
map(f).sequence()
}
}
使用例
(0...4)
.traverse { i in Result<String, Error> { String(i) } }
.ifSuccess { print($0) }
// prints ["0", "1", "2", "3", "4"]
reduce
Result<[Element], E>
の[Element]
にreduce
を適用します。
残念ながらFailure
の型情報が消失します。 Typed Throwsが実装されるのを祈りましょう。
extension Result {
func reduce<T, V>(_ initialResult: T, _ nextPartialResult: (T, V) throws -> T) -> Result<T, Error>
where Success == Array<V> {
mapError { $0 as Error }
.flatMap { a in .init { try a.reduce(initialResult, nextPartialResult) } }
}
}
使用例
(0...4)
.traverse { i in Result<String, Error> { String(i) } }
.reduce("", +)
.ifSuccess { print($0) }
// prints 01234
ご意見お待ちしております!
「それはだめだろ」や「こんなのもあるよ」などのご意見お待ちしております!