13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

SwiftAdvent Calendar 2019

Day 4

[Swift] Resultのために今すぐ書くべきExtension

Last updated at Posted at 2019-12-03

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") }

必要に応じてzip3zip4を用意するのも良いでしょう。

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

ご意見お待ちしております!

「それはだめだろ」や「こんなのもあるよ」などのご意見お待ちしております!

13
5
8

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?