> 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

Reply via email to