(discord/swift/2018/03/15/0) tryとthrowをwrapしたrecover拡張について

概要

  • discord ios dev
    • #swift
    • 2018/03/15
  • tryとthrowをwrapしたrecover拡張について

log

omochimetaru - 2018/03/15

@lovee こんなのできた

public func recover<R>(_ f: () throws -> R, ifError: (Error) -> R) -> R {
    do {
        return try f()
    } catch let error {
        return ifError(error)
    }
}

public func recover<R>(_ f: () throws -> R, ifError: (Error) throws -> R) throws -> R {
    do {
        return try f()
    } catch let error {
        return try ifError(error)
    }
}

この前言っていたguard + tryみたいなパターンは、R をOptional にして使えば

guard let x = recover (...) else { return } の形で書けるし、

2つめのオーバーロードを使えば、リカバリできないエラー型の場合は

そのまま更に上まで飛んで行く。

使用例

        guard let modelScene = recover({ () -> SCNScene? in
            try SCNScene(url: url)
        }, ifError: { error in
            self.handleError(error)
            return nil
        }) else { return }

lovee - 2018/03/15

ふむふむ、だいぶ長くなってしまうところ以外は良さげですね


omochimetaru - 2018/03/15

うん、あとOptional推論がうまくいかないから明示しているんだよね

名前ちょっと変えてかならず Optional になる仕様のほうが使いやすいかも


lovee - 2018/03/15

もしかすると ifError のところと最後のところの戻り値は Optional にすべきだったり?


omochimetaru - 2018/03/15

あ〜 recover 自体を try? でたたむ手があるな

        guard let modelScene = try? recover({
            try SCNScene(url: url)
        }, ifError: { error in
            self.handleError(error)
            throw error
        }) else { return }

lovee - 2018/03/15

できた

import SceneKit

public func recover <R> (_ f: () throws -> R, ifError: (Error) -> Optional<R>) -> Optional<R> {
    do {
        return try f()
    } catch let error {
        return ifError(error)
    }
}

func test() {
    let url = URL(fileURLWithPath: "http://www.google.com")
    guard let _ = recover({ () -> SCNScene in
        try SCNScene(url: url)
    }, ifError: { error in
        print(error)
        return nil
    }) else { return }
}

omochimetaru - 2018/03/15

これだと型ヒントは消せる


lovee - 2018/03/15

ファイル名対応してないんだDiscord


omochimetaru - 2018/03/15

そのパターンでもクロージャの型ヒント消せそう。

        guard let modelScene = recoverToOptional({
            try SCNScene(url: url)
        }, ifError: { error in
            self.handleError(error)
            return nil
        }) else { return }

行数短くしたいですねえ。

        guard let modelScene = recoverToNone({
            try SCNScene(url: url)
        }, ifError: self.handleError) else { return }

lovee - 2018/03/15

よし、ここまで書けた

import Foundation

public func recover <R> (_ f: () throws -> R, ifError: (Error) -> Void) -> Optional<R> {
    do {
        return try f()
    } catch let error {
        ifError(error)
        return nil
    }
}

struct Test {
    
    let url: URL
    
    enum InitError: Error {
        case unknown
    }
    
    init(urlString: String) throws {
        guard let url = URL(string: urlString) else {
            throw InitError.unknown
        }
        self.url = url
    }
    
}

func test(_ string: String) {
    
    guard let test: Test = recover({
        return try Test(urlString: string)
    }, ifError: { print($0) }) else {
        return
    }
    
    print(type(of: test))
    
}

test("http://www.google.com") // Test
test("") // unknown

omochimetaru - 2018/03/15

try test のところのreturnいらなそう

僕はそのrecoverはrecoverToNoneって名前にした


lovee - 2018/03/15

まあ1行だけならね


tarunon - 2018/03/15

nits 第一引数に @autoclosure つけるといいかも


koher - 2018/03/15

↑を読みながら @autoclosure が良さそうだなと思って最後まで読んだら先に言われてた:rolling_eyes:


omochimetaru - 2018/03/15

autoclosureもありだけど個人的には何が起きてるかわかりにくくなりそうなので微妙だなあ


koher - 2018/03/15

用途を考えると single-expression closure になることが多いだろうからまさに @autoclosure の出番だと思うけどなぁ。


omochimetaru - 2018/03/15

まあ特に標準提案したいというほどの気持ちは無いのでお好みで


tarunon - 2018/03/15

多分なんですがthrowsの値を使って何かするみたいなfunction書くときはそもそもそれをthrowsにして、

非throwsとの境界をそこに設定するのが良いのではと思った

func doSomething() {
  return try? doSomething() ?? /* 何か */
}

func doSomething() throws {
  let x = try throwableFunc1()
  let y = try throwableFunc2()
  ...
}

こんなイメージ


omochimetaru - 2018/03/15

↑それだとtry?でnilになるから「throwsの値」(飛んでくるエラーオブジェクトの事だよね)が使えない


tarunon - 2018/03/15

あーー ハマり たしかにそうだ


omochimetaru - 2018/03/15

エラーを使いながらもthrow伝搬を止めて値に畳み込みたいときに、簡単に書くことができないんだよね。

do - catch だと式じゃないから式の中に埋め込めないし。