> On Jan 3, 2016, at 1:37 PM, Dave Abrahams <dabrah...@apple.com> wrote:
> 
>> 
>> On Jan 3, 2016, at 10:21 AM, Matthew Johnson <matt...@anandabits.com> wrote:
>> 
>>> 
>>> On Jan 3, 2016, at 12:12 PM, Dave Abrahams via swift-evolution 
>>> <swift-evolution@swift.org> wrote:
>>> 
>>> 
>>>> On Jan 2, 2016, at 2:23 PM, Tyler Cloutier <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> wrote:
>>>>> 
>>>>> 
>>>>>>> On Dec 27, 2015, at 10:25 PM, Brent Royal-Gordon via swift-evolution 
>>>>>>> <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>
>>>>>>  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. 

I feel like I must be missing something here.  If we are able to mark the whole 
block with try I don’t see how we would notice any really important failure 
points.  They would not be marked anywhere in the source:

  func recognizeHandler() throws {
    try {
        accept(.on)        // .on is an enum tag for the token for the ‘on’ 
keyword.
        recognizeName()
        recognizeFormalParamSeq()
        accept(.newline)
        recognizeCommandSeq()
        accept(.end)
        recognizeName()    // Later Visitor pass checks that names match. 
        accept(.newline)
    }
  }

> 
> 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 don’t disagree in terms of getting error handling right.  

My concern around this is not about getting error handling right or wrong, but 
rather being able to understand what is happening when I read a piece of code, 
especially code I am unfamiliar with.  It is often very helpful to see where 
errors might be thrown.  

If we have a `try` block that removes the requirement to mark potentially 
throwing expressions without any further restrictions many developers will 
overuse that facility.  It will almost surely be the de-facto default by force 
of habit and bad style in a lot of code.  The advantages of the “great default” 
will be lost in this code.  I think this is an important real-world concern to 
keep in mind.

At the same time, I agree that in functions like recognizeHandler where all or 
nearly all statements / expressions can throw making the statements 
individually doesn’t tell us a lot.  It does feel like maybe there should be a 
better way to handle this.

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.

This would immediately tell a reader that a function like recognizeHandler can 
throw on every statement without cluttering up the individual statements.  If 
there were a couple of non-throwing expressions it would still keep things 
cleaner than requiring `try` everywhere:

  func recognizeHandler() throws {
    try {
        accept(do getTheOnTokenValue())        
        recognizeName()
        recognizeFormalParamSeq()
        accept(do getTheNewlineTokenValue())
        recognizeCommandSeq()
        accept(do getTheEndTokenValue())
        recognizeName()  
        accept(do getTheNewlineTokenValue())
    }
  }

The applications of something like this might be pretty limited (because of the 
`do` requirement) but there are are cases like recognizeHandler that would 
benefit from it.

Obviously something like this this wouldn’t help code where a throwing and 
non-throwing code is mixed more evenly.  Either way you would be required to 
mark quite a few expressions.  I would have to see specific examples, but I 
think I would find most such code to be more easily understood with the 
annotations than without.

> 
>> I think positions on both sides of this are reasonable.
> 
> Absolutely.  Even reasonable positions can be sub-optimal though :-)

Agree.  I think the problem here is that both positions on this are sub-optimal 
at different times. :)

> 
>>> 
>>>> 
>>>> 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
>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>> 
>>> -Dave
>>> 
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution@swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
> 
> -Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to