use standard syntax instead of "do" and "repeat"


(Amir Michail) #1

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.


Joining Old Orphaned Posts to the Main Threads
(Brent Royal-Gordon) #2

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.

Do you also propose no longer marking calls to throwing functions with `try`?

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.

···

--
Brent Royal-Gordon
Architechies


(Amir Michail) #3

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.

Do you also propose no longer marking calls to throwing functions with `try`?

Maybe put “throws” after such function calls?

try {
  let z = f(x,y) throws
} catch … {
}

You could also have “throws?” and “throws!” following the function call.

···

On Dec 28, 2015, at 1:25 AM, Brent Royal-Gordon <brent@architechies.com> wrote:

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.

--
Brent Royal-Gordon
Architechies


(Dave Abrahams) #4

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.

-Dave

···

On Dec 27, 2015, at 10:25 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:


(Goffredo Marocchi) #5

One could say that is extremely petty to redefine very commonly accepted words (private when we mean file restricted, internal when we mean essentially package when there are already pretty well understood meaning from languages which quite frankly Swift will not kill now or 5 years from now... Java and C++ will keep dominating the landscape with bigger threats coming from JavaScript, ruby, etc... embracing and extending seems like a more successful strategy than taking a defined word and changing its meaning).

Also, the current do fails the Yoda test... do or do not, no try ;).

I would suggest replacing repeat with do and the current do with something like throwing which the compiler could actually use to generate errors of we are creating a throwing block without any method that could actually throw and I would not touch the current try keyword to minimise changes.

···

Sent from my iPhone

On 28 Dec 2015, at 22:15, Amir Michail via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 28, 2015, at 1:25 AM, Brent Royal-Gordon <brent@architechies.com> 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.

Do you also propose no longer marking calls to throwing functions with `try`?

Maybe put “throws” after such function calls?

try {
let z = f(x,y) throws
} catch … {
}

You could also have “throws?” and “throws!” following the function call.

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.

--
Brent Royal-Gordon
Architechies

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


(Brent Royal-Gordon) #6

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.

That's a really interesting idea, but I don't think it's what the poster was suggesting. It sounded to me like he was merely saying "let's make the Swift error system look like my favorite language's exception system".

···

--
Brent Royal-Gordon
Architechies


(Tyler Cloutier) #7

Please see comments inline.

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.

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, 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

···

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:

-Dave

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


(Amir Michail) #8

One could say that is extremely petty to redefine very commonly accepted words (private when we mean file restricted, internal when we mean essentially package when there are already pretty well understood meaning from languages which quite frankly Swift will not kill now or 5 years from now... Java and C++ will keep dominating the landscape with bigger threats coming from JavaScript, ruby, etc... embracing and extending seems like a more successful strategy than taking a defined word and changing its meaning).

Also, the current do fails the Yoda test... do or do not, no try ;).

I would suggest replacing repeat with do and the current do with something like throwing which the compiler could actually use to generate errors of we are creating a throwing block without any method that could actually throw and I would not touch the current try keyword to minimise changes.

Maybe “throws” instead of “throwing" as in:

throws {
  let z = try f(x, y)
} catch … {
}

···

On Dec 28, 2015, at 6:12 PM, Goffredo Marocchi <panajev@gmail.com> wrote:

Sent from my iPhone

On 28 Dec 2015, at 22:15, Amir Michail via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 28, 2015, at 1:25 AM, Brent Royal-Gordon <brent@architechies.com> 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.

Do you also propose no longer marking calls to throwing functions with `try`?

Maybe put “throws” after such function calls?

try {
let z = f(x,y) throws
} catch … {
}

You could also have “throws?” and “throws!” following the function call.

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.

--
Brent Royal-Gordon
Architechies

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


(Dave Abrahams) #9

Please see comments inline.

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.

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

···

On Jan 2, 2016, at 2:23 PM, Tyler Cloutier <cloutiertyler@aol.com> wrote:

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:


(Goffredo Marocchi) #10

I think we want to leave throws and rethrows at the method signature level and to mean that we want to throw an actual exception so I would not reuse it this way as it would be confusing.

···

Sent from my iPhone

On 29 Dec 2015, at 14:24, Amir Michail <a.michail@me.com> wrote:

On Dec 28, 2015, at 6:12 PM, Goffredo Marocchi <panajev@gmail.com> wrote:

One could say that is extremely petty to redefine very commonly accepted words (private when we mean file restricted, internal when we mean essentially package when there are already pretty well understood meaning from languages which quite frankly Swift will not kill now or 5 years from now... Java and C++ will keep dominating the landscape with bigger threats coming from JavaScript, ruby, etc... embracing and extending seems like a more successful strategy than taking a defined word and changing its meaning).

Also, the current do fails the Yoda test... do or do not, no try ;).

I would suggest replacing repeat with do and the current do with something like throwing which the compiler could actually use to generate errors of we are creating a throwing block without any method that could actually throw and I would not touch the current try keyword to minimise changes.

Maybe “throws” instead of “throwing" as in:

throws {
let z = try f(x, y)
} catch … {
}

Sent from my iPhone

On 28 Dec 2015, at 22:15, Amir Michail via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 28, 2015, at 1:25 AM, Brent Royal-Gordon <brent@architechies.com> 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.

Do you also propose no longer marking calls to throwing functions with `try`?

Maybe put “throws” after such function calls?

try {
let z = f(x,y) throws
} catch … {
}

You could also have “throws?” and “throws!” following the function call.

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.

--
Brent Royal-Gordon
Architechies

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


(Matthew Johnson) #11

Please see comments inline.

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.

I think positions on both sides of this are reasonable.

···

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 <cloutiertyler@aol.com> wrote:

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:

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 Abrahams) #12

Please see comments inline.

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.

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 :slight_smile:

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

···

On Jan 3, 2016, at 10:21 AM, Matthew Johnson <matthew@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 <cloutiertyler@aol.com> wrote:

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:


(Tyler Cloutier) #13

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.

// 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.

Tyler

···

On Jan 3, 2016, at 11:37 AM, Dave Abrahams <dabrahams@apple.com> wrote:

On Jan 3, 2016, at 10:21 AM, Matthew Johnson <matthew@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 <cloutiertyler@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.

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 :slight_smile:

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


(Goffredo Marocchi) #14

Please see comments inline.

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.

In a way the current 'do' (which I would rename to attempt ;)... regardless of requiring try for every throwing statement is used or not I am not a fan of taking a commonly used keyword and changing its meaning unless there is a clear strong benefit to it) already identifies blocks of throwing code and having try marking each expression does not do much about trying to catch errors thrown by too many statements together. My litmus test for this is that we could remove the try keyword from those error handling blocks and it would not by itself worsen our code much, but I may very well be utterly wrong here.

···

On 3 Jan 2016, at 18:21, Matthew Johnson via swift-evolution <swift-evolution@swift.org> 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 <cloutiertyler@aol.com> wrote:

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:

I think positions on both sides of this are reasonable.

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

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


(Matthew Johnson) #15

Please see comments inline.

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 :slight_smile:

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

···

On Jan 3, 2016, at 1:37 PM, Dave Abrahams <dabrahams@apple.com> wrote:

On Jan 3, 2016, at 10:21 AM, Matthew Johnson <matthew@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 <cloutiertyler@aol.com> wrote:

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:

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


(Tyler Cloutier) #16

Please see inline comments.

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", "example@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.

Tyler

···

On Jan 3, 2016, at 6:48 PM, Tyler Fleming Cloutier via swift-evolution <swift-evolution@swift.org> wrote:

Tyler

On Jan 3, 2016, at 11:37 AM, Dave Abrahams <dabrahams@apple.com <mailto:dabrahams@apple.com>> wrote:

On Jan 3, 2016, at 10:21 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@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 <cloutiertyler@aol.com <mailto:cloutiertyler@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> 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 :slight_smile:

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

-Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto: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


(Chris Lattner) #17

Indeed. Also, FWIW, I’d argue that maybe the root problem here is that this code should not be using Swift’s error handling constructs in the first place. An enum (or other approach) may be more appropriate. Swift’s error handling design is intentionally driven by the idea that you shouldn’t use it if “everything throws” - in this situation, the sugar benefits of error handling are intentionally outweighed by the weight of the try keywords everywhere. This is meant to force the balance over to using more manual techniques.

-Chris

···

On Jan 3, 2016, at 6:36 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

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)
    }
  }


(Matthew Johnson) #18

Please see inline comments.

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", "example@example.com <mailto:example@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.

···

On Jan 3, 2016, at 9:31 PM, Tyler Fleming Cloutier via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 3, 2016, at 6:48 PM, Tyler Fleming Cloutier via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Tyler

Tyler

On Jan 3, 2016, at 11:37 AM, Dave Abrahams <dabrahams@apple.com <mailto:dabrahams@apple.com>> wrote:

On Jan 3, 2016, at 10:21 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@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 <cloutiertyler@aol.com <mailto:cloutiertyler@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> 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 :slight_smile:

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

-Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
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

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


(Tyler Cloutier) #19

Please see inline comments.

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", "example@example.com <mailto:example@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.

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

···

On Jan 3, 2016, at 7:38 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 3, 2016, at 9:31 PM, Tyler Fleming Cloutier via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 3, 2016, at 6:48 PM, Tyler Fleming Cloutier via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Tyler

Tyler

On Jan 3, 2016, at 11:37 AM, Dave Abrahams <dabrahams@apple.com <mailto:dabrahams@apple.com>> wrote:

On Jan 3, 2016, at 10:21 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@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 <cloutiertyler@aol.com <mailto:cloutiertyler@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> 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 :slight_smile:

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

-Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
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

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