Swift should implement "Either" type with "?." operator for functional error propagation

Originator:jlieske
Number:rdar://17228902 Date Originated:09-Jun-2014 01:08 AM
Status:Duplicate Resolved:
Product:OS X SDK Product Version:Xcode6 beta: Version 6.0 (6A215l)
Classification:Enhancement Reproducible:Always
 
The Swift language does not support exceptions, which makes it easier to reason about the behavior of code.  However, the current standard library lacks an easy mechanism to propagate errors from computations with results.  

The existing NSErrorPointer mechanism forces code to manually pass the error result holder to every call that can result in an error.  This mechanism works, but it clutters up the code with error-handling boilerplate.

It would be more elegant to return errors as part of a union type. Haskell and Scala have the “Either” type for that; the “Left” case holds errors, and the “Right” case holds success results.  Scala also has the “Try” type, which has monadic behavior like Swift’s Optional type.

I propose adding an enum type called “Result” to the standard Swift library.  It would have “Success” and “Error” cases.  Like Scala’s “Try” monad, it applies functions to “Success” results and simply propagates “Error” results.

In addition, I propose extending Swift’s conditional member access “?.” syntax to work with the “Result” monad.  

The following code sample shows a possible declaration of “Result” along with sample code to exercise it.  It also shows the ideal “?.” syntax in a comment.  
(Note that it currently fails to compile; I have reported that at rdar://17228613)

enum Result<SuccessType, ErrorType> {
    case Success(SuccessType)
    case Error(ErrorType)

    func getLogicValue() -> Bool {
        switch self {
        case .Success(_):
            return true
        case .Error(_):
            return false
        }
    }
    
    func map<U>(f: (SuccessType) -> U) -> Result<U,ErrorType> {
        switch self {
        case let .Success(value):
            return .Success(f(value))
        case let .Error(error):
            return .Error(error)
        }
    }
    
    func bind<U>(f: (SuccessType) -> Result<U,ErrorType>) -> Result<U,ErrorType> {
        switch self {
        case let .Success(value):
            return f(value)
        case let .Error(error):
            return .Error(error)
        }
    }
}

/* Ideal syntax: 
let result = NSData.myApp_dataWithContentsOfFile("filename.json")?.myApp_parseJSON()?.myApp_lookupString("name")?.uppercaseString
*/
let result = NSData.myApp_dataWithContentsOfFile("filename.json")
    .bind({$0.myApp_parseJSON()})
    .bind({$0.myApp_lookupString("name")})
    .map({$0.uppercaseString})

switch result {
case let .Success(value):
    println("Success: "+value)
case let .Error(error):
    println("Error: "+error.localizedDescription)
}

Comments

Apple closed as duplicate of rdar://17158652

NSData and NSDictionary extensions for code sample

extension NSData {
class func myApp_dataWithContentsOfFile(path: String!)
        -> Result<NSData,NSError> {
    var error: NSError?
    if let data = NSData.dataWithContentsOfFile(path,
         options: NSDataReadingOptions(),
         error: &error) {
        return .Success(data)
    }
    else {
        return .Error(error!)
    }
}

func myApp_parseJSON() -> Result<NSDictionary,NSError> {
    var error: NSError?
    if let json = NSJSONSerialization.JSONObjectWithData(self,
    options: NSJSONReadingOptions(),
    error: &error) as? NSDictionary {
        return .Success(json)
    }
    else {
        return .Error(error!)
    }
}

}

extension NSDictionary {
func myApp_lookupString(key: String) -> Result<String,NSError> {
if let value = self.objectForKey(key) as? String {
        return .Success(value)
    }
    else {
        return .Error(NSError(domain: "MyAppDomain", code: 42, userInfo: nil))
    }
}
}

Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!