> On Jan 3, 2016, at 9:31 PM, Tyler Fleming Cloutier via swift-evolution > <swift-evolution@swift.org> wrote: > > Please see inline comments. > > >> On Jan 3, 2016, at 6:48 PM, Tyler Fleming Cloutier via swift-evolution >> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote: >> >> Indeed both are reasonable but perhaps suboptimal. Consider the following >> potential changes. >> >> >> // Assume this code is included for the below examples. >> func myThrowingFunc() throws -> String { >> if drand48() < 0.5 { >> throw NSError(domain: "", code: 0, userInfo: nil) >> } >> return "" >> } >> let str: String >> >> >> >> The current syntax is very clear and straightforward. >> >> // Current syntax >> do { >> str = try myThrowingFunc().stringByAppendingString("appended") >> } catch { >> str = "Default" >> print("caught!") >> } >> >> There are two potential issues with it however. The first is that it is >> quite verbose, and the second is that the try is actually marking two >> function calls, one which throws and one which does not. In this fake >> example it’s clear that myThrowingFunc throws, but in general try is not >> marking a single point of failure. >> >> >> >> One change might be to simply rename do blocks that can throw to try blocks. >> >> // Create try blocks which encapsulate potentially throwing code. >> try { >> str = try myThrowingFunc().stringByAppendingString("appended") >> } catch { >> str = "Default" >> print("caught!") >> } >> >> The motivation for doing this would be to clarify the difference between >> blocks that can throw and blocks that can’t. For example, it’s helpful to >> not have to scroll to the bottom of a long block to find catch, or scan >> through all the lines to find the try keyword for a long block. You would be >> able to see just from try that block was throwing. It would also be similar >> to many other languages that use try to demarcate throwing blocks. The >> problems with this are that it could be considered redundant, and is even >> more verbose (by 1 character) than the current syntax. Furthermore, as with >> the current syntax, try is not marking a single point of failure (and yet >> now we have to try keywords). >> >> >> >> Another change could be to rename do blocks that can throw to try blocks and >> then not require explicit marking of try on throwing statements. >> >> // Don't require explicit try marking within try blocks. >> try { >> str = myThrowingFunc().stringByAppendingString("appended") >> } catch { >> str = "Default" >> print("caught!") >> } >> >> This approach retains all of the benefits of the above change, including >> familiarity for those coming from other languages. Also, it no longer >> requires the redundant double try syntax. In this case try is not assumed to >> be marking a single potentially failing call, but a group of them. >> Unfortunately, this means that it might not be clear which function is the >> function that can throw, in a block of code. However, this is already >> somewhat the case for chained calls in the current syntax. Certainly, only >> allowing this ambiguity for chained calls reduces the potential size of the >> code that is unmarked, with functional paradigms long chains are not so >> uncommon. >> >> >> >> The final change that I have included above is really just a shortening of >> syntax and could be applied to any of the above implementations to reduce >> verbosity. >> > > I have included below* > > >> // Allow catch directly on try expression. >> let str = try myThrowingFunc().stringByAppendingString("appended”) catch { >> str = "Default" >> print("caught!") >> } >> >> This also has the added benefit of not having to open up a new scope just to >> catch an error. Additionally it’s very easy to refactor into a try? >> statement. >> >> I’d really like to see how these changes might affect real world examples >> and if I get some time, I will look for some and share them with the list. >> That way we can really see what the effects of these changes would be within >> the context of an actual use case. >> >>> What would you think about a solution that just inverted the default. >>> Rather than marking throwing expressions with `try` we could have a try >>> block (with optional catch clauses) where non-throwing calls are marked >>> with `do`. The primary motivation for requiring `do` would be to prevent >>> abuse of `try` blocks by making them awkward when there is a reasonable mix >>> of throwing and non-throwing code. A secondary benefit is that would still >>> be clear what can throw and what can’t, although this is much less useful >>> when most things can throw. >> >> Also Matthew, I think this is an interesting idea. And I’d say you’ve hit on >> the major problem with try blocks, potential excessive mixing of throwing >> and non throwing code. You could perhaps enforce that try blocks must begin >> and end with a potentially throwing statement to cut down on mixing, but >> people might find that strange/confusing. >> > > You might even compromise to allow try blocks, but only in the case where > every single statement can throw. This would at least solve the problem of > many try statements in a row. This situation seems reasonably common. The > following is from a cursory search of the SwiftPM source. > > try popen(["git", "-C", dstdir, "init"]) > try popen(["git", "-C", dstdir, "config", "user.email", "exam...@example.com > <mailto:exam...@example.com>"]) > try popen(["git", "-C", dstdir, "config", "user.name", "Example Example"]) > try popen(["git", "-C", dstdir, "add", "."]) > try popen(["git", "-C", dstdir, "commit", "-m", "msg"]) > try popen(["git", "-C", dstdir, "tag", tag]) > > That solves at least some of the problem.
That seems a bit excessive. Why not allow non-throwing expressions but require them to be marked by `do`? That covers the same use-case while still allowing a little bit of flexibility. > > Tyler > >> Tyler >> >>> On Jan 3, 2016, at 11:37 AM, Dave Abrahams <dabrah...@apple.com >>> <mailto:dabrah...@apple.com>> wrote: >>> >>>> >>>> On Jan 3, 2016, at 10:21 AM, Matthew Johnson <matt...@anandabits.com >>>> <mailto:matt...@anandabits.com>> wrote: >>>> >>>>> >>>>> On Jan 3, 2016, at 12:12 PM, Dave Abrahams via swift-evolution >>>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote: >>>>> >>>>> >>>>>> On Jan 2, 2016, at 2:23 PM, Tyler Cloutier <cloutierty...@aol.com >>>>>> <mailto:cloutierty...@aol.com>> wrote: >>>>>> >>>>>> Please see comments inline. >>>>>> >>>>>>> On Dec 31, 2015, at 12:07 PM, Dave Abrahams via swift-evolution >>>>>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote: >>>>>>> >>>>>>> >>>>>>>>> On Dec 27, 2015, at 10:25 PM, Brent Royal-Gordon via swift-evolution >>>>>>>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote: >>>>>>>>> >>>>>>>>> So “try” instead of “do”. If there is no catch, then just use braces >>>>>>>>> without a keyword for a block. >>>>>>>>> >>>>>>>>> And use do-while instead of repeat-while. >>>>>>> >>>>>>> +1 >>>>>>> >>>>>>>> >>>>>>>> Do you also propose no longer marking calls to throwing functions with >>>>>>>> `try`? >>>>>>> >>>>>>> If try had both a single-statement/expression form as it does today, >>>>>>> and a block form that makes it unnecessary to mark all the individual >>>>>>> statements in the block, that would be an improvement. >>>>>>> >>>>>>>> Have you read the "Error-Handling Rationale" document in the Swift >>>>>>>> repository? If not, please do: >>>>>>>> <https://github.com/apple/swift/blob/master/docs/ErrorHandlingRationale.rst >>>>>>>> >>>>>>>> <https://github.com/apple/swift/blob/master/docs/ErrorHandlingRationale.rst>> >>>>>>>> If so, please explain why you disagree with it. >>>>>>> >>>>>>> There are large classes of programs where you can know you don’t care >>>>>>> exactly where a failure happens, e.g. (most init functions, all pure >>>>>>> functions, any function that doesn’t break invariants). In these cases >>>>>>> marking every statement or expression that can throw is just noise. >>>>>>> Try writing some serialization/deserialization code where the >>>>>>> underlying stream can fail to see what I mean; you’ll have “try” >>>>>>> everwhere, and it adds nothing to comprehensibility or maintainability. >>>>>>> Personally I would like to be able to label the function itself and >>>>>>> not have to introuce a scope, but IMO being able to create “try blocks” >>>>>>> would be a welcome addition and would even match the common case in >>>>>>> blocks with catch clauses, where being aware of the exact line where >>>>>>> the error was generated is typically not useful. >>>>>> >>>>>> I had proposed something very similar to this around six months ago on >>>>>> the swift-users list, but I think John McCall, had some (quite valid) >>>>>> concerns with this. >>>>>> >>>>>> Unfortunately I can't access those emails, but I think his concern was >>>>>> that the purpose of try was to mark explicitly which statements throw >>>>>> and this would defeat the purpose of that. People might just wrap large >>>>>> blocks in try. >>>>> >>>>> As much as I am loath to disagree with John on this, there’s an incorrect >>>>> implicit assumption in that rationale, that forcing people to mark all >>>>> throw points trains them to get error-handling correct. What it does >>>>> instead is to train them to think of all code uniformly instead of >>>>> recognizing the places where a throw needs special attention (places >>>>> where there are broken invariants). Eventually, as with warnings that >>>>> have a high false-positive rate, when you see “try” in many places where >>>>> it doesn’t help, you learn to ignore it altogether. >>>> >>>> I agree that requiring this is not likely to result in improved error >>>> handling and thus is not a strong argument in favor of it. >>>> >>>> IMO the purpose of requiring “try” to be stated explicitly is that it >>>> arguably makes code more readable. It is immediately clear which >>>> functions can throw and which cannot. You don’t need to look up the >>>> signature of every function called to determine this. My experience thus >>>> far has been that I have really appreciated the requirement that throwing >>>> expressions be explicitly marked. >>> >>> As a default it’s great. Not having a way to opt out of individual marking >>> for a whole block or function—because you know you’re not breaking any >>> invariants, so which functions can throw is irrelevant, and not having a >>> way for the compiler deduce these regions (e.g. known pure functions)—is >>> the problem. The recognizer code posted in an earlier message is a perfect >>> example. If there *was* some code where it was really important to notice >>> failure points, you’d miss it. >>> >>> The key to getting error handling right is not being able to trace every >>> possible control path—which is effectively impossible anyway— it’s >>> understanding the relationship between scopes in your code and your >>> program’s invariants. >>> >>>> I think positions on both sides of this are reasonable. >>> >>> Absolutely. Even reasonable positions can be sub-optimal though :-) >>> >>>>> >>>>>> >>>>>> Another idea is to treat the block as an unnamed, no argument, no return >>>>>> value, function that could throw. This solves the problem in a very >>>>>> general way, and would retain the marking of all throwing functions with >>>>>> try, >>>>> >>>>> That marking, in itself, is the root problem. Our syntax is the way it >>>>> is primarily because "marking everywhere" was adopted as an explicit goal. >>>>> >>>>>> but has the perhaps unfortunate syntax of allowing things like: >>>>>> >>>>>> try { >>>>>> try myFunction() >>>>>> } catch { >>>>>> >>>>>> } >>>>>> >>>>>> Something like this could be shortened to a consistent theoretical >>>>>> inline try catch syntax like: >>>>>> >>>>>> try myFunction() catch { >>>>>> >>>>>> } >>>>>> >>>>>> Though, as John, pointed out at the time, this could still be added on >>>>>> with the current syntax. Obviously treating a try like an unnamed >>>>>> function would have different return semantics, so perhaps that's not >>>>>> the right abstraction. (Although I recall a thread going on that is >>>>>> considering allowing functions to retain return semantics of the outer >>>>>> scope) >>>>>> >>>>>> Tyler >>>>>> >>>>>> >>>>>>> >>>>>>> -Dave >>>>>>> >>>>>>> _______________________________________________ >>>>>>> swift-evolution mailing list >>>>>>> swift-evolution@swift.org <mailto:swift-evolution@swift.org> >>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution> >>>>> >>>>> -Dave >>>>> >>>>> _______________________________________________ >>>>> swift-evolution mailing list >>>>> swift-evolution@swift.org <mailto:swift-evolution@swift.org> >>>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution> >>> >>> -Dave >> >> _______________________________________________ >> swift-evolution mailing list >> swift-evolution@swift.org <mailto:swift-evolution@swift.org> >> https://lists.swift.org/mailman/listinfo/swift-evolution >> <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 > <https://lists.swift.org/mailman/listinfo/swift-evolution>
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution