> 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

Reply via email to