[swift-evolution] [Proposal]: Escaping another (unused) scope pyramide with 'guard try catch'
Dany St-Amant
dsa.mls at icloud.com
Fri Feb 5 21:40:03 CST 2016
> Le 5 févr. 2016 à 14:33, Adrian Zubarev via swift-evolution <swift-evolution at swift.org> a écrit :
>
> @Erica I wrote this example before:
>
> do {
> let listener = try TCPListener("some address")
>
> do {
> let conn = try listener.accept()
>
> // do some work and catch more errors
>
> do {
> try conn.close()
> } catch {
> do {
> try listener.close()
> } catch {
> fatallError("cannot close listener")
> }
> fatallError("cannot close connection")
> }
> } catch {
> fatallError("cannot accept")
> }
> } catch {
> fatallError("cannot create listener")
> }
>
I am no expert (and have always be scared of throws) but from what I understand the errors thrown by each method can be specific, so you can do multiple try in a single do (even though I could not find example on the web); flattening your pyramid a little bit.
do {
let listener = try TCPListener("some address")
let conn = try listener.accept()
// Some more code
} catch SomeError.CannotCreate {
fatallError("cannot create listener")
} catch SomeError.Rejection {
fatallError("cannot accept")
} catch {
fatallError("Unknown")
}
So once restructured, is the pyramid still that bad?
Dany
> Than I realized I could avoid this pyramide of doom by doing it differently:
>
> let listener: TCPListener
> do {
> listener = try TCPListener("some address")
> } catch {
> fatallError("cannot create listener")
> }
>
> let conn: TCPConn
> do {
> conn = try listener.accept()
> } catch {
> fatallError("cannot accept")
> }
>
> // do some work and catch more errors
>
> do {
> try conn.close()
> } catch {
> do {
> try listener.close()
> } catch {
> fatallError("cannot close listener")
> }
> fatallError("cannot close connection")
> }
>
> As you can see there is no need for the do block here. A single try-statement would do enough.
>
> I could rewrite this example like this:
>
> guard let listener = try TCPListener("some address") catch {
> fatallError("cannot create listener") // or return and try again later
> }
>
> guard let conn = try listener.accept() catch {
> fatallError("cannot accept")
> }
>
> // do some work and catch more errors
>
> do try conn.close() catch {
> do try listener.close() catch {
> fatallError("cannot close listener")
> }
> fatallError("cannot close connection")
> }
>
> --
> Adrian Zubarev
> Sent with Airmail
>
> Am 5. Februar 2016 bei 20:18:26, Adrian Zubarev (adrian.zubarev at devandartist.com <mailto:adrian.zubarev at devandartist.com>) schrieb:
>
>> +1 You are right Félix, it was only my own project that tempted me to return from each catch scope.
>>
>> But wouldn’t this confuse everyone?
>>
>> Here is an example:
>>
>> do let/var newInstance = try throwingFuncReturns() catch {
>> // can not fall through at all
>> }
>>
>> There is no chance the compile can guarantee that `newInstance` was assigned with a value, except if `newInstance` is an optional type, so this code may not fall through like the 'guard‘ mechanism.
>>
>> let newInstance: Type
>> do newInstance = try throwingFuncReturns() catch {
>> // if not returned from here or the newInstance is not set here the compiler should raise an error
>> }
>>
>> `newInstance` is only safe when throwing function returns without any errors or the instance was set inside the catch scope. This mimics the `if else` behavior. This can already be done with the existing `do try catch` mechanism.
>>
>> let x: SomeThing
>> if condition {
>> x = foo()
>> } else {
>> x = bar()
>> }
>> use(x)
>>
>> do try throwingFunc() catch {
>> // can fall through if needed (this means I can ignore the error or return/break)
>> }
>>
>> Another way do this right now would look like this:
>>
>> do {
>> try throwingFunc()
>> } catch _ { } // will ignore the error
>>
>> As you can see only the first example can not fall though after catching the error, so wouldn’t it be better to support both syntax variants?
>>
>> guard let/var newInstance = try throwingFuncReturns() catch {
>> // can not fall through
>> }
>>
>> let newInstance: Type
>> do newInstance = try throwingFuncReturns() catch {
>> // if not returned from here or the newInstance is not set here the compiler should raise an error
>> }
>>
>> do try throwingFunc() catch {
>> // can fall through if needed (this means I can ignore the error)
>> }
>>
>> --
>> Adrian Zubarev
>> Sent with Airmail
>>
>> Am 5. Februar 2016 bei 19:40:46, Félix Cloutier (felixcca at yahoo.ca <mailto:felixcca at yahoo.ca>) schrieb:
>>
>>> Unless you're declaring a variable with a single-statement try, there's no reason to disallow fallthrough.
>>>
>>> Félix
>>>
>>>> Le 5 févr. 2016 à 13:28:58, Adrian Zubarev via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> a écrit :
>>>>
>>>> Right now the error handling mechanism will fall through even if the error was handled inside the catch scope.
>>>>
>>>> enum Error: ErrorType {
>>>> case SomeError
>>>> }
>>>>
>>>> func throwingFunc() throws {
>>>> throw Error.SomeError
>>>> }
>>>>
>>>> func throwingFuncReturns() throws -> Int {
>>>> return 0
>>>> }
>>>>
>>>> do { try throwingFunc() } catch {
>>>> /* do nothing */
>>>> }
>>>>
>>>> var num: Int
>>>> do { num = try throwingFuncReturns() } catch {
>>>> /* do nothing */
>>>> }
>>>>
>>>> print("both fell through")
>>>>
>>>> To guarantee the safety of the execution from my point of view the catch body may not fall through (for single try statements), at least not by default.
>>>>
>>>> I think it is fine to rename the proposed mechanism.
>>>>
>>>> do try throwingFunc() catch {
>>>> /* handle error */
>>>> }
>>>>
>>>> do try throwingFunc() catch _ {
>>>> /* handle error */
>>>> }
>>>>
>>>> do try throwingFunc() catch pattern {
>>>> /* handle error */
>>>> }
>>>>
>>>> do try throwingFunc() catch pattern {
>>>> /* handle error */
>>>> }
>>>>
>>>> do let newInstance = try throwingFuncReturns() catch catch pattern where condition {
>>>> /* handle error */
>>>> }
>>>>
>>>> do var newMutableInstance = try throwingFuncReturns() catch pattern where condition {
>>>> /* handle error */
>>>> }
>>>>
>>>> 'Where' clause is significant for catching wider range of errors that might occur.
>>>>
>>>> --
>>>> Adrian Zubarev
>>>> Sent with Airmail
>>>>
>>>> Am 5. Februar 2016 bei 19:05:12, Félix Cloutier (felixcca at yahoo.ca <mailto:felixcca at yahoo.ca>) schrieb:
>>>>
>>>>> We could do it without a new guard syntax if `do` didn't need a block statement.
>>>>>
>>>>> Félix
>>>>>
>>>>>> Le 5 févr. 2016 à 12:54:47, Adrian Zubarev via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> a écrit :
>>>>>>
>>>>>> Hello dear Swift community,
>>>>>>
>>>>>> this proposal might seem like some new syntax sugar, but it also aims to escape the 'do { }' scope from the 'do try catch‘ mechanism while making the existing error handling more powerful.
>>>>>>
>>>>>> Lets assume we have some sort of network type, which can throw a ton of different errors:
>>>>>>
>>>>>> struct TCPListener {
>>>>>> init(address: String) throws { /* implement */ }
>>>>>>
>>>>>> func accept() throws -> TCPConn { /* implement */ }
>>>>>>
>>>>>> /* ... */
>>>>>> }
>>>>>>
>>>>>> A way of implimentation might look like this:
>>>>>>
>>>>>> let listener: TCPListener
>>>>>> do {
>>>>>> listener = try TCPListener("some valid address")
>>>>>>
>>>>>> // we could do more work here, but if we need to catch more
>>>>>> // errors we will result in a new PYRAMIDE OF DOOM
>>>>>> } catch {
>>>>>> fatalError()
>>>>>> }
>>>>>>
>>>>>> At this point think about the comment inside the 'do { }' scope. Such an application might result in a new pyramide of doom as we know from optional unwrapping before 'guard else' mechanism was introduced.
>>>>>>
>>>>>> let clientConn: TCPConn
>>>>>> do {
>>>>>> clientConn = try listener.accept() // save to call accept method
>>>>>> } catch {
>>>>>> fatalError()
>>>>>> }
>>>>>>
>>>>>> As you can see this application might not need the extra 'do' scope at all, and if it does, the 'do try catch' is still there.
>>>>>>
>>>>>> I propose a new error handling mechanism that mimics the solution for optional pyramide of doom, which adds a slightly better syntax and removes the unneeded/unused 'do { }' scope (as for the example from above). Not only can this mechanism guarantee the execution of a throwing function without any errors (like a true guard condition) it also can assign returned values to a new constant/variable.
>>>>>>
>>>>>> Introducing the 'guard try catch' mechanism:
>>>>>>
>>>>>> guard try throwingFunc() catch {
>>>>>> /* handle error */
>>>>>> }
>>>>>>
>>>>>> guard try throwingFunc() catch _ {
>>>>>> /* handle error */
>>>>>> }
>>>>>>
>>>>>> guard try throwingFunc() catch pattern {
>>>>>> /* handle error */
>>>>>> }
>>>>>>
>>>>>> guard try throwingFunc() catch pattern where condition {
>>>>>> /* handle error */
>>>>>> }
>>>>>>
>>>>>> guard let newInstance = try throwingFuncReturns() catch ... {
>>>>>> /* handle error */
>>>>>> }
>>>>>>
>>>>>> Where '...' represents the different combinations of possible patterns already showed in the first 4 examples.
>>>>>>
>>>>>> We also might want the return type to be mutable.
>>>>>>
>>>>>> guard var newMutableInstance = try throwingFuncReturns() catch ... {
>>>>>> /* handle error */
>>>>>> }
>>>>>>
>>>>>> This mechanism also makes the error handling more powerful, since it can catch more specific errors defined with 'where condition'.
>>>>>>
>>>>>> Lets rebuild the example from above with the new mechanism:
>>>>>>
>>>>>> guard let listener = try TCPListener("some valid address") catch {
>>>>>> fatalError()
>>>>>> }
>>>>>>
>>>>>> guard let clientConn = try listener.accept() catch {
>>>>>> fatalError()
>>>>>> }
>>>>>>
>>>>>> One think that should be mentioned here is that the method call from the second 'guard' is safe, because a 'guard' body may not fall through.
>>>>>>
>>>>>> Impact on existing codebase: None, because the mechanism is new and does not break any existing code.
>>>>>>
>>>>>> I'm really curious about your opinions.
>>>>>> _______________________________________________
>>>>>> swift-evolution mailing list
>>>>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160205/c0986d6e/attachment.html>
More information about the swift-evolution
mailing list