> On Jan 3, 2016, at 7:38 PM, Matthew Johnson <[email protected] > <mailto:[email protected]>> wrote: > >> >> On Jan 3, 2016, at 9:31 PM, Tyler Fleming Cloutier via swift-evolution >> <[email protected] <mailto:[email protected]>> wrote: >> >> Please see inline comments. >> >> >>> On Jan 3, 2016, at 6:48 PM, Tyler Fleming Cloutier via swift-evolution >>> <[email protected] <mailto:[email protected]>> 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", "[email protected] >> <mailto:[email protected]>"]) >> 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. >
Yeah, I noted in my previous email that that is indeed an interesting idea. Though, I think the issue that some might have with that is that they probably want to be able to see very quickly which statements in a function cause it to throw via uncaught tries. It adds a bit of complexity to have to notice that the statements are in a try block and then mentally invert the context to look for statements not marked with do. Tyler >> >> Tyler >> >>> Tyler >>> >>>> On Jan 3, 2016, at 11:37 AM, Dave Abrahams <[email protected] >>>> <mailto:[email protected]>> wrote: >>>> >>>>> >>>>> On Jan 3, 2016, at 10:21 AM, Matthew Johnson <[email protected] >>>>> <mailto:[email protected]>> wrote: >>>>> >>>>>> >>>>>> On Jan 3, 2016, at 12:12 PM, Dave Abrahams via swift-evolution >>>>>> <[email protected] <mailto:[email protected]>> wrote: >>>>>> >>>>>> >>>>>>> On Jan 2, 2016, at 2:23 PM, Tyler Cloutier <[email protected] >>>>>>> <mailto:[email protected]>> wrote: >>>>>>> >>>>>>> Please see comments inline. >>>>>>> >>>>>>>> On Dec 31, 2015, at 12:07 PM, Dave Abrahams via swift-evolution >>>>>>>> <[email protected] <mailto:[email protected]>> wrote: >>>>>>>> >>>>>>>> >>>>>>>>>> On Dec 27, 2015, at 10:25 PM, Brent Royal-Gordon via swift-evolution >>>>>>>>>> <[email protected] <mailto:[email protected]>> 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 >>>>>>>> [email protected] <mailto:[email protected]> >>>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>>>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution> >>>>>> >>>>>> -Dave >>>>>> >>>>>> _______________________________________________ >>>>>> swift-evolution mailing list >>>>>> [email protected] <mailto:[email protected]> >>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution> >>>> >>>> -Dave >>> >>> _______________________________________________ >>> swift-evolution mailing list >>> [email protected] <mailto:[email protected]> >>> https://lists.swift.org/mailman/listinfo/swift-evolution >>> <https://lists.swift.org/mailman/listinfo/swift-evolution> >> >> _______________________________________________ >> swift-evolution mailing list >> [email protected] <mailto:[email protected]> >> https://lists.swift.org/mailman/listinfo/swift-evolution >> <https://lists.swift.org/mailman/listinfo/swift-evolution>
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
