Do try catch?


(Don Arnel) #1

Is there a reason this format was adopted for error handling:

do {
    try makeASandwich()
    eatASandwich()
} catch Error.OutOfCleanDishes {
    washDishes()
} catch Error.MissingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

rather than this format:

try {
    makeASandwich()
    eatASandwich()
} catch Error.OutOfCleanDishes {
    washDishes()
} catch Error.MissingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

The second format is much more intuitive, and reads easier IMO.


(Slava Pestov) #2

Hi Don,

This is just my opinion, but I prefer the explicit 'try' on each statement that can throw, because then it is easier to see where all the early returns are from a function, in case any resources have to be cleaned up with a 'defer'.

Slava

···

On Dec 7, 2015, at 6:30 AM, Don Arnel via swift-evolution <swift-evolution@swift.org> wrote:

Is there a reason this format was adopted for error handling:

do {
    try makeASandwich()
    eatASandwich()
} catch Error.OutOfCleanDishes {
    washDishes()
} catch Error.MissingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

rather than this format:

try {
    makeASandwich()
    eatASandwich()
} catch Error.OutOfCleanDishes {
    washDishes()
} catch Error.MissingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

The second format is much more intuitive, and reads easier IMO.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Chris Lattner) #3

Yes:

-Chris

···

On Dec 7, 2015, at 6:30 AM, Don Arnel via swift-evolution <swift-evolution@swift.org> wrote:

Is there a reason this format was adopted for error handling:


(Wei Wang) #4

The first (current) choice is better in fact.

With try closure out of the scope, you could not tell exactly which statement would throw.
In your example, only `makeSandwich` would throw, so there is no need to mark `eatASandwich` with a `try`.

Current design follows minimal conception very well, you could get to know that only the `makeASandwich` would throw an error, which reduce a lot of noise when reading this code.

Best regards.

···

---
Sincerely,
Wei Wang (王巍, @onevcat)
im.onevcat.com <http://im.onevcat.com/>

在 2015年12月7日,下午11:30,Don Arnel via swift-evolution <swift-evolution@swift.org> 写道:

Is there a reason this format was adopted for error handling:

do {
    try makeASandwich()
    eatASandwich()
} catch Error.OutOfCleanDishes {
    washDishes()
} catch Error.MissingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

rather than this format:

try {
    makeASandwich()
    eatASandwich()
} catch Error.OutOfCleanDishes {
    washDishes()
} catch Error.MissingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

The second format is much more intuitive, and reads easier IMO.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Liam Butler-Lawrence) #5

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

On Dec 7, 2015, at 9:36 AM, 王巍 via swift-evolution <swift-evolution@swift.org> wrote:

The first (current) choice is better in fact.

With try closure out of the scope, you could not tell exactly which statement would throw.
In your example, only `makeSandwich` would throw, so there is no need to mark `eatASandwich` with a `try`.

Current design follows minimal conception very well, you could get to know that only the `makeASandwich` would throw an error, which reduce a lot of noise when reading this code.

Best regards.
---
Sincerely,
Wei Wang (王巍, @onevcat)
im.onevcat.com <http://im.onevcat.com/>

在 2015年12月7日,下午11:30,Don Arnel via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> 写道:

Is there a reason this format was adopted for error handling:

do {
    try makeASandwich()
    eatASandwich()
} catch Error.OutOfCleanDishes {
    washDishes()
} catch Error.MissingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

rather than this format:

try {
    makeASandwich()
    eatASandwich()
} catch Error.OutOfCleanDishes {
    washDishes()
} catch Error.MissingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

The second format is much more intuitive, and reads easier IMO.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Liam Butler-Lawrence) #6

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@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 :slight_smile:

On 07 Dec 2015, at 16:24, Liam Butler-Lawrence via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@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

On Dec 7, 2015, at 9:36 AM, 王巍 via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

The first (current) choice is better in fact.

With try closure out of the scope, you could not tell exactly which statement would throw.
In your example, only `makeSandwich` would throw, so there is no need to mark `eatASandwich` with a `try`.

Current design follows minimal conception very well, you could get to know that only the `makeASandwich` would throw an error, which reduce a lot of noise when reading this code.

Best regards.
---
Sincerely,
Wei Wang (王巍, @onevcat)
im.onevcat.com <http://im.onevcat.com/>

在 2015年12月7日,下午11:30,Don Arnel via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> 写道:

Is there a reason this format was adopted for error handling:

do {
    try makeASandwich()
    eatASandwich()
} catch Error.OutOfCleanDishes {
    washDishes()
} catch Error.MissingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

rather than this format:

try {
    makeASandwich()
    eatASandwich()
} catch Error.OutOfCleanDishes {
    washDishes()
} catch Error.MissingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

The second format is much more intuitive, and reads easier IMO.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Davide De Franceschi) #7

I think what you want here (pretty desirable in many situations) is more like a "guard catch" alternative to the usual "guard else" :slight_smile:

···

On 7 Dec 2015, at 15:24, Liam Butler-Lawrence via swift-evolution <swift-evolution@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

On Dec 7, 2015, at 9:36 AM, 王巍 via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

The first (current) choice is better in fact.

With try closure out of the scope, you could not tell exactly which statement would throw.
In your example, only `makeSandwich` would throw, so there is no need to mark `eatASandwich` with a `try`.

Current design follows minimal conception very well, you could get to know that only the `makeASandwich` would throw an error, which reduce a lot of noise when reading this code.

Best regards.
---
Sincerely,
Wei Wang (王巍, @onevcat)
im.onevcat.com <http://im.onevcat.com/>

在 2015年12月7日,下午11:30,Don Arnel via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> 写道:

Is there a reason this format was adopted for error handling:

do {
    try makeASandwich()
    eatASandwich()
} catch Error.OutOfCleanDishes {
    washDishes()
} catch Error.MissingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

rather than this format:

try {
    makeASandwich()
    eatASandwich()
} catch Error.OutOfCleanDishes {
    washDishes()
} catch Error.MissingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

The second format is much more intuitive, and reads easier IMO.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Kametrixom Tikara) #8

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 :slight_smile:

···

On 07 Dec 2015, at 16:24, Liam Butler-Lawrence via swift-evolution <swift-evolution@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

On Dec 7, 2015, at 9:36 AM, 王巍 via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

The first (current) choice is better in fact.

With try closure out of the scope, you could not tell exactly which statement would throw.
In your example, only `makeSandwich` would throw, so there is no need to mark `eatASandwich` with a `try`.

Current design follows minimal conception very well, you could get to know that only the `makeASandwich` would throw an error, which reduce a lot of noise when reading this code.

Best regards.
---
Sincerely,
Wei Wang (王巍, @onevcat)
im.onevcat.com <http://im.onevcat.com/>

在 2015年12月7日,下午11:30,Don Arnel via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> 写道:

Is there a reason this format was adopted for error handling:

do {
    try makeASandwich()
    eatASandwich()
} catch Error.OutOfCleanDishes {
    washDishes()
} catch Error.MissingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

rather than this format:

try {
    makeASandwich()
    eatASandwich()
} catch Error.OutOfCleanDishes {
    washDishes()
} catch Error.MissingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

The second format is much more intuitive, and reads easier IMO.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Kametrixom Tikara) #9

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@me.com <mailto:liamdunn@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@icloud.com <mailto:kametrixom@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 :slight_smile:

On 07 Dec 2015, at 16:24, Liam Butler-Lawrence via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@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@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Liam Butler-Lawrence) #10

Hi Kame,

Thanks for the work you put into this! I’ll give my thoughts on each proposed syntax:

// 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
    }

This is comprehensive and seems (to me, anyway!) like it could definitely work. My only concern is that the similar-but-different syntaxes for the different optional/try combinations could be a bit confusing. I see the following three unique syntaxes in your proposal:

    guard let x = y, try z(a) else { } catch E { }... catch { }
    guard let x = try z(a) else { } catch E { }... catch { }
    guard try z(a) catch E { }... catch { }

That’s not even considering where conditions, or chaining more than a single optional and try together in a single guard expression.

What if one wanted to put a try clause before an optional? In that case, when is let required to prefix an unwrapped = optional clause? Currently, it is only required at the beginning of the chain. If the chain starts with a try, is let still required first, or simply at the first instance of an unwrapped = optional, or not at all? The second option might look something like this:

    guard try z(a), let x = y, c = d else { } catch E { }... catch { }

Note: I'm ignoring the possibility of guard var… as proposal 0003 <https://github.com/apple/swift-evolution/blob/master/proposals/0003-remove-var-parameters-patterns.md> has already been accepted

···

****

    // 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
        }
    }

I like this a lot. However, I think going with this version would move this proposal from a medium-level syntax change (changing or adding an option for where errors are caught) to a fundamental change to the error handling-model (how errors are caught). Accepting that, we could go one step further and eliminate the boilerplate switch { }:

    guard try foo("Hello"), let x = try qux() catch {
        case Error.Some: return
        default: return
    }

This basically turns catch { } into a specialized switch statement. I don’t think this would cause any functionality issues, as case X: case Y: default: syntax already works in exactly the same manner as catch X { } catch Y { } catch { }. Even where conditions that can currently be applied to catch should work just fine as case X where X. The only thing to really consider would be the syntax when what was thrown is irrelevant. You proposed this:

    guard let bar = bar, let _ = try qux() where baz == true else {
        return
    } catch {
        return
    }

Unfortunately, this seems incongruent with my idea of turning catch into an implied switch statement. Perhaps

    guard let bar = bar, let _ = try qux() where baz == true else {
        return
    } catch {
        default: return
    }

though I can’t say I love this approach.

Of course, most of the considerations I mentioned from syntax #1 (regarding clarity in optional/try chaining) apply to both versions of #2 as well. That said, putting all the catch “cases" in one { } block is far more concise and could possibly make those considerations less of an issue.

****

    // 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 removing catch is a bad idea. Not just because almost every other language uses it for exception/error handling, but because it is a natural parallel to throw. It makes sense on an intuitive level, and I don’t think we should mess with that. Not to mention the else block could easily get very messy very fast.

Thanks again! I appreciate your comments on my suggestions as well :slight_smile:

Liam

On Dec 7, 2015, at 2:35 PM, Kametrixom Tikara <kametrixom@icloud.com <mailto:kametrixom@icloud.com>> wrote:

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@me.com <mailto:liamdunn@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


(Liam Butler-Lawrence) #11

Bumping this up. If anyone on the Apple team (or anybody else!) has any feedback that would be much appreciated. Also, I’d be happy to write up an official proposal if requested- the suggested guideline was to have a thorough discussion first, which this really hasn’t had yet.

Thanks!
Liam

···

On Dec 7, 2015, at 11:30 PM, Liam Butler-Lawrence via swift-evolution <swift-evolution@swift.org> wrote:

Hi Kame,

Thanks for the work you put into this! I’ll give my thoughts on each proposed syntax:

// 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
    }

This is comprehensive and seems (to me, anyway!) like it could definitely work. My only concern is that the similar-but-different syntaxes for the different optional/try combinations could be a bit confusing. I see the following three unique syntaxes in your proposal:

    guard let x = y, try z(a) else { } catch E { }... catch { }
    guard let x = try z(a) else { } catch E { }... catch { }
    guard try z(a) catch E { }... catch { }

That’s not even considering where conditions, or chaining more than a single optional and try together in a single guard expression.

What if one wanted to put a try clause before an optional? In that case, when is let required to prefix an unwrapped = optional clause? Currently, it is only required at the beginning of the chain. If the chain starts with a try, is let still required first, or simply at the first instance of an unwrapped = optional, or not at all? The second option might look something like this:

    guard try z(a), let x = y, c = d else { } catch E { }... catch { }

Note: I'm ignoring the possibility of guard var… as proposal 0003 <https://github.com/apple/swift-evolution/blob/master/proposals/0003-remove-var-parameters-patterns.md> has already been accepted

****

    // 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
        }
    }

I like this a lot. However, I think going with this version would move this proposal from a medium-level syntax change (changing or adding an option for where errors are caught) to a fundamental change to the error handling-model (how errors are caught). Accepting that, we could go one step further and eliminate the boilerplate switch { }:

    guard try foo("Hello"), let x = try qux() catch {
        case Error.Some: return
        default: return
    }

This basically turns catch { } into a specialized switch statement. I don’t think this would cause any functionality issues, as case X: case Y: default: syntax already works in exactly the same manner as catch X { } catch Y { } catch { }. Even where conditions that can currently be applied to catch should work just fine as case X where X. The only thing to really consider would be the syntax when what was thrown is irrelevant. You proposed this:

    guard let bar = bar, let _ = try qux() where baz == true else {
        return
    } catch {
        return
    }

Unfortunately, this seems incongruent with my idea of turning catch into an implied switch statement. Perhaps

    guard let bar = bar, let _ = try qux() where baz == true else {
        return
    } catch {
        default: return
    }

though I can’t say I love this approach.

Of course, most of the considerations I mentioned from syntax #1 (regarding clarity in optional/try chaining) apply to both versions of #2 as well. That said, putting all the catch “cases" in one { } block is far more concise and could possibly make those considerations less of an issue.

****

    // 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 removing catch is a bad idea. Not just because almost every other language uses it for exception/error handling, but because it is a natural parallel to throw. It makes sense on an intuitive level, and I don’t think we should mess with that. Not to mention the else block could easily get very messy very fast.

Thanks again! I appreciate your comments on my suggestions as well :slight_smile:

Liam

On Dec 7, 2015, at 2:35 PM, Kametrixom Tikara <kametrixom@icloud.com <mailto:kametrixom@icloud.com>> wrote:

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@me.com <mailto:liamdunn@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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(John McCall) #12

This proposal packs an awful lot into a single “guard” statement, but that might be okay; it’s just something that I personally am not totally comfortable with.

The larger problem that I see is that this changes the semantic behavior of “guard” quite a bit. Normally, in something like:

  guard let x = foo(),
        bar()
    else { … }

these clauses are both expected to be conditional: that is, foo() is expected to return an optional type, bar() is expected to return a Bool, etc. The guard protects against the exceptional conditions. But this isn’t really what you want, because the exceptional condition you’re looking to guard against is whether the expression threw. Thus, in the corresponding example:

  guard let x = try foo(),
        try bar()
    catch { … }
  
it should at the very least be acceptable for foo() to return a non-optional type, and if it doesn’t, it’s debatable whether you really want to unwrap it; and similarly, it might be reasonable for bar() to return Void, e.g.:

  guard try registerUser()
    catch { … }

Part of this is just the original sin that “if let” and “guard let” aren't very composable. For example, you run into a very similar problem with normal “guard” just by trying to bind a non-optional value between two other conditions, e.g.:
  guard let resolver = factory.currentResolver,
        let numResolved = resolver.resolvedEntityCount,
        numResolved < maxResolutionCount
    else { … }

However, this new pattern for guard really exacerbates the problem, both because it’s so common to have functions with non-optional results that throw errors, and because even when the function has an optional result, it’s ambiguous whether you really want to simultaneously unwrap it. In other words, there’s too much magic in this syntax, and it seems to be colliding.

One possible solution is to say that “guard let" only unwraps results if there’s an else clause. (What happens with “guard try foo()” in this case?) But that means that adding and removing an else clause has very subtle and hard-to-explain semantic effects on the semantics of “guard”. I’m tempted to say that we just shouldn’t allow the combination of an else clause and catch clauses, but that’s just begging for people to ask for the combination, at which point we’ll have codified two incompatible models for the statement.

John.

···

On Dec 7, 2015, at 8:30 PM, Liam Butler-Lawrence via swift-evolution <swift-evolution@swift.org> wrote:
Hi Kame,

Thanks for the work you put into this! I’ll give my thoughts on each proposed syntax:

// 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
    }

This is comprehensive and seems (to me, anyway!) like it could definitely work. My only concern is that the similar-but-different syntaxes for the different optional/try combinations could be a bit confusing. I see the following three unique syntaxes in your proposal:

    guard let x = y, try z(a) else { } catch E { }... catch { }
    guard let x = try z(a) else { } catch E { }... catch { }
    guard try z(a) catch E { }... catch { }

That’s not even considering where conditions, or chaining more than a single optional and try together in a single guard expression.


(John McCall) #13

Bumping this up. If anyone on the Apple team (or anybody else!) has any feedback that would be much appreciated. Also, I’d be happy to write up an official proposal if requested- the suggested guideline was to have a thorough discussion first, which this really hasn’t had yet.

I’m sorry, I’ve had a lot of other significant designs to look at recently, and I’ve been rate-limiting them so I don’t neglect all the other work on my plate. :slight_smile: I’ll try to take a look at this sometime next week; is that alright?

John.

···

On Dec 11, 2015, at 6:11 PM, Liam Butler-Lawrence via swift-evolution <swift-evolution@swift.org> wrote:

Thanks!
Liam

On Dec 7, 2015, at 11:30 PM, Liam Butler-Lawrence via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi Kame,

Thanks for the work you put into this! I’ll give my thoughts on each proposed syntax:

// 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
    }

This is comprehensive and seems (to me, anyway!) like it could definitely work. My only concern is that the similar-but-different syntaxes for the different optional/try combinations could be a bit confusing. I see the following three unique syntaxes in your proposal:

    guard let x = y, try z(a) else { } catch E { }... catch { }
    guard let x = try z(a) else { } catch E { }... catch { }
    guard try z(a) catch E { }... catch { }

That’s not even considering where conditions, or chaining more than a single optional and try together in a single guard expression.

What if one wanted to put a try clause before an optional? In that case, when is let required to prefix an unwrapped = optional clause? Currently, it is only required at the beginning of the chain. If the chain starts with a try, is let still required first, or simply at the first instance of an unwrapped = optional, or not at all? The second option might look something like this:

    guard try z(a), let x = y, c = d else { } catch E { }... catch { }

Note: I'm ignoring the possibility of guard var… as proposal 0003 <https://github.com/apple/swift-evolution/blob/master/proposals/0003-remove-var-parameters-patterns.md> has already been accepted

****

    // 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
        }
    }

I like this a lot. However, I think going with this version would move this proposal from a medium-level syntax change (changing or adding an option for where errors are caught) to a fundamental change to the error handling-model (how errors are caught). Accepting that, we could go one step further and eliminate the boilerplate switch { }:

    guard try foo("Hello"), let x = try qux() catch {
        case Error.Some: return
        default: return
    }

This basically turns catch { } into a specialized switch statement. I don’t think this would cause any functionality issues, as case X: case Y: default: syntax already works in exactly the same manner as catch X { } catch Y { } catch { }. Even where conditions that can currently be applied to catch should work just fine as case X where X. The only thing to really consider would be the syntax when what was thrown is irrelevant. You proposed this:

    guard let bar = bar, let _ = try qux() where baz == true else {
        return
    } catch {
        return
    }

Unfortunately, this seems incongruent with my idea of turning catch into an implied switch statement. Perhaps

    guard let bar = bar, let _ = try qux() where baz == true else {
        return
    } catch {
        default: return
    }

though I can’t say I love this approach.

Of course, most of the considerations I mentioned from syntax #1 (regarding clarity in optional/try chaining) apply to both versions of #2 as well. That said, putting all the catch “cases" in one { } block is far more concise and could possibly make those considerations less of an issue.

****

    // 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 removing catch is a bad idea. Not just because almost every other language uses it for exception/error handling, but because it is a natural parallel to throw. It makes sense on an intuitive level, and I don’t think we should mess with that. Not to mention the else block could easily get very messy very fast.

Thanks again! I appreciate your comments on my suggestions as well :slight_smile:

Liam

On Dec 7, 2015, at 2:35 PM, Kametrixom Tikara <kametrixom@icloud.com <mailto:kametrixom@icloud.com>> wrote:

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@me.com <mailto:liamdunn@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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Liam Butler-Lawrence) #14

Hi John,

Sounds great. I appreciate the response!

Liam

···

Sent from my iPhone

On Dec 11, 2015, at 9:39 PM, John McCall <rjmccall@apple.com> wrote:

On Dec 11, 2015, at 6:11 PM, Liam Butler-Lawrence via swift-evolution <swift-evolution@swift.org> wrote:
Bumping this up. If anyone on the Apple team (or anybody else!) has any feedback that would be much appreciated. Also, I’d be happy to write up an official proposal if requested- the suggested guideline was to have a thorough discussion first, which this really hasn’t had yet.

I’m sorry, I’ve had a lot of other significant designs to look at recently, and I’ve been rate-limiting them so I don’t neglect all the other work on my plate. :slight_smile: I’ll try to take a look at this sometime next week; is that alright?

John.

Thanks!
Liam

On Dec 7, 2015, at 11:30 PM, Liam Butler-Lawrence via swift-evolution <swift-evolution@swift.org> wrote:

Hi Kame,

Thanks for the work you put into this! I’ll give my thoughts on each proposed syntax:

// 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
    }

This is comprehensive and seems (to me, anyway!) like it could definitely work. My only concern is that the similar-but-different syntaxes for the different optional/try combinations could be a bit confusing. I see the following three unique syntaxes in your proposal:

    guard let x = y, try z(a) else { } catch E { }... catch { }
    guard let x = try z(a) else { } catch E { }... catch { }
    guard try z(a) catch E { }... catch { }

That’s not even considering where conditions, or chaining more than a single optional and try together in a single guard expression.

What if one wanted to put a try clause before an optional? In that case, when is let required to prefix an unwrapped = optional clause? Currently, it is only required at the beginning of the chain. If the chain starts with a try, is let still required first, or simply at the first instance of an unwrapped = optional, or not at all? The second option might look something like this:

    guard try z(a), let x = y, c = d else { } catch E { }... catch { }

Note: I'm ignoring the possibility of guard var… as proposal 0003 has already been accepted

****

    // 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
        }
    }

I like this a lot. However, I think going with this version would move this proposal from a medium-level syntax change (changing or adding an option for where errors are caught) to a fundamental change to the error handling-model (how errors are caught). Accepting that, we could go one step further and eliminate the boilerplate switch { }:

    guard try foo("Hello"), let x = try qux() catch {
        case Error.Some: return
        default: return
    }

This basically turns catch { } into a specialized switch statement. I don’t think this would cause any functionality issues, as case X: case Y: default: syntax already works in exactly the same manner as catch X { } catch Y { } catch { }. Even where conditions that can currently be applied to catch should work just fine as case X where X. The only thing to really consider would be the syntax when what was thrown is irrelevant. You proposed this:

    guard let bar = bar, let _ = try qux() where baz == true else {
        return
    } catch {
        return
    }

Unfortunately, this seems incongruent with my idea of turning catch into an implied switch statement. Perhaps

    guard let bar = bar, let _ = try qux() where baz == true else {
        return
    } catch {
        default: return
    }

though I can’t say I love this approach.

Of course, most of the considerations I mentioned from syntax #1 (regarding clarity in optional/try chaining) apply to both versions of #2 as well. That said, putting all the catch “cases" in one { } block is far more concise and could possibly make those considerations less of an issue.

****

    // 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 removing catch is a bad idea. Not just because almost every other language uses it for exception/error handling, but because it is a natural parallel to throw. It makes sense on an intuitive level, and I don’t think we should mess with that. Not to mention the else block could easily get very messy very fast.

Thanks again! I appreciate your comments on my suggestions as well :slight_smile:

Liam

On Dec 7, 2015, at 2:35 PM, Kametrixom Tikara <kametrixom@icloud.com> wrote:

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@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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution