How can I implement a custom error throwing syntax in swift?

时间秒杀一切 提交于 2020-07-22 06:39:29

问题


I want to implement the following:

throwingFunction()??.doStuff()
/*  if throwingFunction throws an error:
     print the error
  else 
    returns an object with the doStuff() Method 
*/
throwingFunction()??
/*
 if an error is thrown, 
    prints the error.  
 else 
    execute the function without errors. 
*/

I'm not sure where to look in the source code for examples on how do, try, catch were implemented. The Swift error docs explain how to use error handle methods that are already implemented. To be clear, I want to implement custom error handling with the above syntax.

Something like:

precedencegroup Chaining {
    associativity: left
}

infix operator ?? : Chaining

extension Result {
    
  // ERROR: Unary operator implementation must have a 'prefix' or 'postfix' modifier
    static func ??(value: Result<Success, Failure>) -> Success? { 
        switch value {
        case .success(let win):
            return win
        case .failure(let fail):
            print(fail.localizedDescription)
            return nil
        }
    }
}

回答1:


You can define a postfix operator which takes a throwing closure as (left) operand. ?? is already defined as an infix operator, therefore you have to choose a different name:

postfix operator <?>

postfix func <?><T>(expression: () throws -> T) -> T? {
    do {
        return try expression()
    } catch {
        print(error)
        return nil
    }
}

Now you can call

let result = throwingFunc<?>

or chain it with

let result = (throwingFunc<?>)?.doStuff()

Previous answer:

?? is already defined as an infix operator. For a postfix operator you have to choose a different name, for example:

postfix operator <?>

extension Result {
    static postfix func <?>(value: Result) -> Success? {
        switch value {
        case .success(let win):
            return win
        case .failure(let fail):
            print(fail.localizedDescription)
            return nil
        }
    }
}

Now you can call

let res = Result(catching: throwingFunc)<?>

or chain it with

let res = (Result(catching: throwingFunc)<?>)?.doStuff()



回答2:


There is little chance that you can do this, without actually forking apple/swift and creating your own version of the compiler... Here are my attempts:

First, I noticed that the second part of the desired result, ?.doStuff() looks exactly like a optional chaining expression. I thought I could make a postfix ? operator that returned an optional. But it turns out, I can't declare an ? operator at all:

postfix operator ? // error

So I used a visually similar character - - instead. The type of a throwing function is () throws -> Void and I used @autoclosure so that the {} can be omitted:

typealias ThrowingFunction<T> = () throws -> T

postfix operator ‽
postfix func ‽<T>(lhs: @autoclosure ThrowingFunction<T>) -> T? {
    switch Result(catching: lhs) {
    case .failure(let error):
        print(error.localizedDescription)
        return nil
    case .success(let t):
        return t
    }
}

// Usage:
func f() throws -> Int {
    throw URLError(URLError.badURL)
}

// You have to use it like this :(
(try f()‽)
(try f()‽)?.description

The try could be omitted if the function you are calling takes no arguments:

f‽
(f‽)?.description

To make functions of other arity work without try, you need to create an implementation of for each arity, which sucks.

But the brackets must be there because of how Swift parses operators :(

Then I tried to make the approach you attempted, with key paths:

func ??<T, U>(lhs: @autoclosure ThrowingFunction<T>, rhs: KeyPath<T, U>) -> U? {
    switch Result(catching: lhs) {
    case .failure(let error):
        print(error.localizedDescription)
        return nil
    case .success(let t):
        return t[keyPath: rhs]
    }
}

func f() throws -> Int {
    throw URLError(URLError.badServerResponse)
}

This seems to be even worse, because you gotta use it like this:

try f() ?? \.description

You can't omit the try,

f ?? \.description // type inferencer freaks out, thinks T is ThrowingFunction<Int>

nor reduce the spaces on either side of ?? (See here for why):

try f()??\.description

Plus there is this backlash that is an integral part of the keypath syntax, and you can only use it for key paths, not methods. :(

Summary

You can't do this because:

  • You can't overload ?
  • You can't put a ? right after anything because it will be parsed as optional chaining
  • You must write try, unless you cater for every arity.



回答3:


postfix operator *

@discardableResult
postfix func *<Preferred>(expression: ErrorAlt<Preferred>) -> Preferred? {
    switch expression {
    case .preferred(let pref):
        return pref
    case .error(let err):
        print(err.localizedDescription)
        return nil
    case .initializersWereNil:
        print("initializersWereNil")
        return nil
    }
}

Here is an example usage.

enum TestMeError: Error {
    case first
}

extension Int {
    func printWin() {
        print("we did it!")
    }
}

func testMe() -> ErrorAlt<Int> {
    if true {
        return .error(TestMeError.first)
    } else {
        return .preferred(40)
    }
}

// USAGE
testMe()*


来源:https://stackoverflow.com/questions/62711205/how-can-i-implement-a-custom-error-throwing-syntax-in-swift

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!