Willam Hill Willam Hill - 4 years ago 208
Swift Question

Overloading ?? operator with Result type

I'm reading the Functional Swift book and to demonstrate Optionals, Chapter 8 mentions how

??
operator could be defined to work on Result type, a previously defined type. However, when I attempt to utilize this operator, I don't see the overloaded version getting invoked. Am I using this operator incorrectly?

// Define ErrorType
enum LookupError: ErrorType {
case CapitalNotFound
case PopulationNotFound
}

enum Result<T> {
case Success(T)
case Error(ErrorType)
}

// Overloaded operator
func ??<T>(result: Result<T>, handleError: ErrorType -> T) -> T {
print("overloaded result: \(result)")
switch result {
case let .Success(value): return value
case let .Error(error): return handleError(error)
}
}


I'm utilizing the operator like this:

let r:Result<String>? = Result<String>.Success("A-Ok")
let d = Result<String>.Success("Error")
let result = r ?? d


Doing this shows:

Result: Success("A-Ok")


But at no point does the overloaded operator get invoked.

Answer Source

You're expression let result = r ?? d does not match the function signature of your overload, and hence it doesn't know that it's your function that you want to call (and calls the regular nil coalescing operator instead).

Let's take a look at the signature:

func ??<T>(result: Result<T>, handleError: ErrorType -> T) -> T { ...

You use the first argument in your call almost correctly, it should be of type Result<T>, however not an optional. The 2nd parameter in your custom ?? function, however, is a closure, a function taking type ErrorType and returning generic T (in our example: String), and the second argument in your call must be of such a closure type. Hence, your two Result<String> instances r and d should not be used together with your custom ?? operator, but on their own together with the appropriate closure.

Define your two example instances of Result<String>---one containing an error and one containing a success---as

var r:Result<String> = Result<String>.Success("A-Ok")
var d:Result<String> = Result<String>.Error(LookupError.CapitalNotFound)

Now, to conform to your ?? function signature in our calls to it, we construct the right hand side of your expression to be a closure as follows:

var result = r ?? {
    err in
        switch err as! LookupError {
        case .CapitalNotFound: return "Couldn't find capital"
        case .PopulationNotFound: return "Couldn't find population"
        }
    }
print(result) /* A-Ok */

result = d ?? {
    err in
    switch err as! LookupError {
    case .CapitalNotFound: return "Couldn't find capital"
    case .PopulationNotFound: return "Couldn't find population"
    }
}
print(result) /* Couldn't find capital */

Ok, now we actually call your ?? overload. These expressions above, however, looks quite messy, especially if we want to call our custom ?? operator frequently.

Instead of posting the same closure each time you perform your error check, we can create a handle to the closure that we make use of instead. The handle is used---just like the closure above---as the right hand side argument in the call to your custom ?? operator:

func myStringErrHandler () -> (ErrorType -> String) {
    return { (err: ErrorType) -> (String) in
        switch err as! LookupError {
        case .CapitalNotFound: return "Couldn't find capital"
        case .PopulationNotFound: return "Couldn't find population"
    }
}
let errHandle = myStringErrHandler()

var result = r ?? errHandle
print(result) /* A-Ok */

result = d ?? errHandle
print(result) /* Couldn't find capital */

This could probably be done even neater, but you get the gist of it: we must make sure our call and its arguments conform to the signature of our custom function.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download