[swift-evolution] do try catch?
Liam Butler-Lawrence
liamdunn at me.com
Mon Dec 7 22:30:12 CST 2015
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 :)
Liam
> On Dec 7, 2015, at 2:35 PM, Kametrixom Tikara <kametrixom at icloud.com <mailto:kametrixom at 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 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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151207/ce87ef3f/attachment.html>
More information about the swift-evolution
mailing list