[swift-evolution] do try catch?

Kametrixom Tikara kametrixom at icloud.com
Mon Dec 7 13:35:29 CST 2015


Hi Liam, this is how I imagine it working, I proposed three slightly different syntax rules:

enum Error : ErrorType { case Some }

func test(bar: Int?, baz: Bool?, foo: String throws -> Void, qux: () throws -> Double) {
    // Currently possible:
    do {
        try foo("Hello")
        let x = try qux()
    } catch Error.Some {
        print("Error!")
    } catch {
        print("Error!")
    }
    
    // First syntax:
    guard let
        bar = bar,  // Has to be non-nil
        try foo("Hello"),   // Has to not throw an error
        x = try qux()
        where bar > 10
    else {    // Has to not throw an error
        return  // Bar or baz was nil
    } catch Error.Some {
        return  // Some Error occured
    } catch {
        return  // Another Error occured
    }
    
    // `else` has to be there for optional, `catch` for errors
    guard try foo("Hello"), let x = try qux() where x < 10 catch Error.Some {
        return
    } catch {
        return
    }
    
    // Results can be ignored; catch can be on new line
    guard let
        _ = try foo("Hello"),
        _ = try qux()
    catch Error.Some {  // Not 100% beautiful
        return
    } catch {
        return
    }
    
    
    // Second syntax, no error matching, my personal preference
    
    guard try foo("Hello"), let x = try qux() catch {
        switch error {
        case Error.Some: return
        default: return
        }
    }
    
    guard let bar = bar, let _ = try qux() where baz == true else {
        return
    } catch {
        return
    }
    
    
    // Third syntax, `else` for everything, implicit `error` variable when a `try` was used in the guard
    
    guard try foo("Hello") else {
        switch error {
        case Error.Some: return
        default: return
        }
    }
}

I think this feels right at home in Swift, `guard` indicating that some condition has to be met or else exit the scope. This syntax

>> let result = try test() catch SomeError.SomeCase {
>>     
>> } catch {
>>     
>> }

you proposed doesn’t make it really clear that the scope has to be exited but I like it nonetheless. This also is in the same spirit as the recent discussion on making `if else`, `switch case` and such work as expression.

– Kame


> On 07 Dec 2015, at 18:40, Liam Butler-Lawrence <liamdunn at me.com <mailto:liamdunn at me.com>> wrote:
> 
> Hey Kametrixom,
> 
> Thanks for the feedback! I agree that the proposed syntax needs improvement.
> 
>> guard let unwrapped = optional, result = try test() else {
>>     
>> } catch SomeError.SomeCase {
>>     
>> } catch {
>>     
>> }
> 
> 
> I like this example, but it seems rather specific. Are you just using the  guard let unwrapped = optional to show how guard let and the catch block could be used together, or suggesting that guard let should be required for this functionality? If the latter, how would we handle throwing functions that don’t return a value?
> 
> I’m fine with removing the enclosing catch { } from my original suggestion; perhaps something like this? My only concern with this is that it’s slightly confusing to have the first catch X on the same line as the try call.
> 
>> let result = try test() catch SomeError.SomeCase {
>>     
>> } catch {
>>     
>> }
> 
> 
> Also, regarding the new syntax being redundant:
> 
>> let coordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
>> 
>> do {
>>     let documentsDirectoryURL = try NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false)
>>     
>>     let persistentStoreFileName = "CoreDataStore.sqlite"
>>     let persistentStoreURL = documentsDirectoryURL.URLByAppendingPathComponent(persistentStoreFileName)
>>     
>>     try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: persistentStoreURL, options: nil)
>>     
>> } catch MyError.Case1 {
>>     
>> } catch MyError.Case2 {
>>     
>> } catch MyError.Case3 {
>>     
>> } catch AnotherError.Case1 {
>>     
>> } catch AnotherError.Case2 {
>>     
>> } catch AnotherError.Case3 {
>>     
>> } catch {
>>     
>> }
> 
> Thanks for pointing out this possibility. However, this code makes it even harder to decipher where MyError will be thrown from. How about AnotherError– which function threw that? No way to know. In certain cases that may not be an issue, but what if the error handling code presented a UI error? I might want to know more than just what the error is, but the context in which it was generated as well. Putting all the catch blocks at the end directly impacts readability.
> 
> As an extension of this issue, suppose that both throwing functions threw the same kind of error. If I want to use a different response based on which function call actually threw the error, I simply can’t do that using this method.
> 
> Look forward to hearing your thoughts. Thanks again!
> 
> Liam
> 
>> On Dec 7, 2015, at 10:49 AM, Kametrixom Tikara <kametrixom at icloud.com <mailto:kametrixom at icloud.com>> wrote:
>> 
>> Hi Liam
>> 
>> I really like that idea, maybe the syntax needs a bit of adjustment. What do you think about this:
>> 
>> guard let unwrapped = optional, result = try test() else {
>>     
>> } catch SomeError.SomeCase {
>>     
>> } catch {
>>     
>> }
>> 
>> However I don’t really know if this is needed since it’s possible to call as many throwing functions as you want in a do-catch block:
>> 
>> let coordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
>> 
>> do {
>>     let documentsDirectoryURL = try NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false)
>>     
>>     let persistentStoreFileName = "CoreDataStore.sqlite"
>>     let persistentStoreURL = documentsDirectoryURL.URLByAppendingPathComponent(persistentStoreFileName)
>>     
>>     try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: persistentStoreURL, options: nil)
>>     
>> } catch MyError.Case1 {
>>     
>> } catch MyError.Case2 {
>>     
>> } catch MyError.Case3 {
>>     
>> } catch AnotherError.Case1 {
>>     
>> } catch AnotherError.Case2 {
>>     
>> } catch AnotherError.Case3 {
>>     
>> } catch {
>>     
>> }
>> 
>> Which makes the need of such a feature redundant. I really like the idea and would love this to be added :)
>> 
>>> On 07 Dec 2015, at 16:24, Liam Butler-Lawrence via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>> 
>>> I completely agree Wei. Using try as a keyword placed before every throwing function is one of the greatest strengths of Swift’s error-handling model.
>>> 
>>> I do have a new proposal regarding do/try/catch (if I should post this in a new thread, please let me know):
>>> 
>>> ————
>>> 
>>> The problem:
>>> 
>>> In Swift 1.0, the only way to conditionally unwrap an optional was with if let. This ended up causing the “pyramid of doom” so often that in Swift 2.0, guard let was introduced.
>>> 
>>> The same problem is present with do/try/catch. Consider this code snippet:
>>> 
>>> let coordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
>>> 
>>> do {
>>>     
>>>     let documentsDirectoryURL = try NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false)
>>>     
>>>     let persistentStoreFileName = "CoreDataStore.sqlite"
>>>     let persistentStoreURL = documentsDirectoryURL.URLByAppendingPathComponent(persistentStoreFileName)
>>>     
>>>     do {
>>>         try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: persistentStoreURL, options: nil)
>>>     }
>>>     catch AnotherError.Case1 {
>>>         
>>>     }
>>>     catch AnotherError.Case2 {
>>>         
>>>     }
>>>     catch AnotherError.Case3 {
>>>         
>>>     }
>>>     catch {
>>>         
>>>     }
>>> 
>>> }
>>> catch MyError.Case1 {
>>>     
>>> }
>>> catch MyError.Case2 {
>>>     
>>> }
>>> catch MyError.Case3 {
>>>     
>>> }
>>> catch {
>>>     
>>> }
>>> 
>>> First, yes, I know that URLForDirectory() doesn’t throw MyError. I’m illustrating the common pattern using actual function calls.
>>> 
>>> I see two problems with the above code:
>>> 
>>> 1. The standard execution of code (if no errors are thrown) gets more and more nested with every call to a throwing function, causing a pyramid of doom. There’s only two levels in this example, but it could easily be four or five.
>>> 
>>> 2. The first try NSFileManager.default… is no less than 20 lines away from its catch statement. Again, with more try statements, this number only increases, making the code very unreadable in my opinion.
>>> 
>>> 
>>> A possible solution:
>>> 
>>> Allow a catch clause to be used directly after any function that throws. This clause includes all the catch x {} catch y {} error-handling code. Just like the else clause in a guard let, this catch clause has to return or otherwise break normal execution. Because of this, the overarching do {} catch {} blocks are no longer needed, and normal execution continues without continually increasing code indentation.
>>> 
>>> This is how the above code snippet might look if this solution was implemented:
>>> 
>>> let coordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
>>> 
>>> let documentsDirectoryURL = try NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false) catch {
>>>     catch MyError.Case1 {
>>>         
>>>     }
>>>     catch MyError.Case2 {
>>>         
>>>     }
>>>     catch MyError.Case3 {
>>>         
>>>     }
>>>     catch {
>>>         
>>>     }
>>> }
>>> let persistentStoreFileName = "CoreDataStore.sqlite"
>>> let persistentStoreURL = documentsDirectoryURL.URLByAppendingPathComponent(persistentStoreFileName)
>>> 
>>> try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: persistentStoreURL, options: nil) catch {
>>>     catch AnotherError.Case1 {
>>>         
>>>     }
>>>     catch AnotherError.Case2 {
>>>         
>>>     }
>>>     catch AnotherError.Case3 {
>>>         
>>>     }
>>>     catch {
>>>         
>>>     }
>>> }
>>> 
>>> The syntax of the new catch block could certainly be improved on (it might also benefit from typed throwing as discussed in another thread). However, even imperfect, this code is much more readable than what’s currently required, as one can instantly tell which try statement goes with which catch statements. In addition, the the pyramid of doom is gone, and the resulting code is reminiscent of a function with many guard lets.
>>> 
>>> If you have any feedback or know of potential issues that I’m not aware of, I look forward to hearing from you. Thanks so much!
>>> 
>>> Liam
>>> 
>>>  _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
> 

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151207/dc8c4345/attachment.html>


More information about the swift-evolution mailing list