问题
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