> And finally, even if an operator function could fail in multiple ways (we're 
> really getting to very hypothetical hypotheticals here), writing `try!` all 
> the time might look silly and non-Swift users might then mock the language, 
> but I dispute the contention that it would make things "unbearable.”

The whole point of ‘try’/’try!’ is to make the user consider how to handle the 
error cases.  If it gets used everywhere, then you have a boy who cried wolf 
situation where it is seen as noise and ignored… which definitely affects 
usability. (Take Windows' error dialogs as an example of this phenomenon).



> On Jan 14, 2017, at 7:29 PM, Xiaodi Wu <[email protected]> wrote:
> 
> On Sat, Jan 14, 2017 at 8:03 PM, Jonathan Hull <[email protected] 
> <mailto:[email protected]>> wrote:
> My intended framing of this does not seem to be coming across in my 
> arguments.  I am not thinking of this as a way to avoid typing ‘try!’ or 
> ‘try?’.  This is not intended to replace any of the current uses of ‘throws’. 
>  Rather, it is intended to replace trapping and nil-returning functions where 
> converting it to throw would be burdensome in the most common use cases, but 
> still desirable in less common use cases.  In my mind, it is only enabling 
> the author to provide extra information and flexibility, compared to the 
> current behavior.
> 
> For example, let’s say I have a failable initializer, which could fail for 2 
> or 3 different reasons, and that the vast majority of use-cases I only care 
> whether it succeeded or not (which is why nil-returning was chosen)… but 
> there may be a rare case or two where I really would prefer to probe deeper 
> (and changing it to a throwing initializer would inhibit the majority cases). 
>  Then using ’throws?’ allows the primary usage to remain unchanged, while 
> allowing users to opt-in to throwing behavior when desired.
> 
> Right now I end up making multiple functions, which are identical except for 
> throw vs nil-return, and must now be kept in sync.  I’ll admit it isn’t 
> terribly common, but it has come up enough that I think it would still be 
> useful.
> 
> As you say, I think this is a pretty niche use case. When you are in control 
> of the code, it's trivial to write a second function that wraps the throwing 
> function, returning an optional value on error. The only thing you'd need to 
> keep in sync would be the declaration, not the function body, and that isn't 
> truly onerous on the rare occasion when this is at issue.
> 
> 
> The other argument I will make is one of symmetry.  We have 3 different types 
> of error handling in swift: throwing, optional-returns, and trapping.
> 
> As the Error Handling Rationale document has pointed out, these three 
> different types of error handling are meant for different _kinds_ of error. 
> The idea is that ideally the choice of what kind of error handling to use 
> shouldn't be down to taste or other arbitrary criteria, but should reflect 
> whether we're dealing with a recoverable error (throws), simple domain error 
> (return nil), or logical error (trap). That much can be determined at the 
> point of declaration. At the use site, there are tools to allow the end user 
> to handle these errors in a variety of ways, but there is a logic behind 
> allowing conversions between some and not all combinations:
> 
> * A logical error is meant to be unrecoverable and thus cannot be converted 
> to either nil or throw. To call a function that traps is to assert that the 
> function's preconditions are met. If it's a possibility that the 
> preconditions cannot be met, it should be handled before calling the 
> function. A trap represents a programming mistake that should be fixed by 
> changing the code so as not to trap. There are adequate solutions to the few 
> instances where an error that currently traps might not be always have to be 
> fatal: in the case of array indices, for instance, there's been proposals to 
> allow more lenient subscripting that don't trap, at the cost of extra 
> overhead--of course, you can already implement this for yourself in an 
> extension.
> 
> * A simple domain error fails in only one obvious way and doesn't need an 
> error; the end user can always decide that a failure should be handled by 
> trapping using `!`--in essence, the user is asserting that the occurrence of 
> a simple domain error at that use site is a logical error. It shouldn't be 
> useful to convert nil to an error, because a simple domain error should be 
> able to fail in only one way; if the function fails in more than one way, the 
> function should throw, as it's no longer a simple domain error.
> 
> * A recoverable error can fail in one or more ways, and how you recover may 
> depend on how it failed; a user can always decide that they'll always recover 
> in the same way by using `try?`, or they can assert that it's a logical error 
> to fail at all using `try!`. The choice is up to the user.
> 
> As far as I can tell, `throws?` and `throws!` do not change these choices; it 
> simply says that a recoverable error should be handled by default as a simple 
> domain error or a logical error, which in the Swift error handling model 
> should be up to the author who's using the function and not the author who's 
> declaring it.
>  
> There is already some ability to convert between these:
> 
> If you have a throwing function:
>       ‘try?’ allows you to convert to optional-return
>       ‘try!’ allows you to convert to trapping
> 
> If you have an optional-return:
>       ‘!’ allows you to convert to trapping
>       you are unable to convert to throwing (because it requires extra info 
> which isn’t available)
> 
> If you have a trapping function, you are unable to convert to either.
> 
> With ‘throws?’ you have an optional return which you can convert to throwing 
> with ‘try’
> 
> With ‘throws!’ you have a trapping function where:
>       ‘try?’ allows you to convert to optional-return
>       ‘try’ allows you to convert to throwing
> 
> 
> Thus, ‘throws?’ and ‘throws!’ allow you provide optional-return and trapping 
> functions where extra information is provided so that it is possible to 
> convert to throwing when desired.  In cases where this conversion is not 
> appropriate, the author would simply continue to use the current methods.
> 
> Basically it is useful in designs where optional-return or trapping were 
> ultimately chosen, but there was also a strong case to be made for making it 
> a throwing function.
> 
> This is totally the opposite use case from that outlined above. Here, you 
> don't control the code and the original author decided to return an optional 
> value or to trap. In essence, you're saying that the original author made a 
> mistake, and what the author considered to be an unrecoverable error should 
> be recoverable. However, you won't be able to squeeze useful errors out of it 
> unless you write additional diagnostic logic yourself. This is already 
> possible to do in an extension, where you can add a throwing function that 
> checks the arguments before forwarding to the failable or trapping function. 
> As far as I can tell, `throws!` doesn't provide you with any more tools to do 
> so.
> 
> I think the fears of people using it instead of ‘throws’ are unfounded 
> because they already have the ability to use optionals or trapping… this just 
> mitigates some of the losses from those choices. 
> 
> Does that make more sense?
> 
> Maybe I'm misunderstanding something. An author that writes a function that 
> throws offers the greatest number of choices to their end users for how to 
> handle errors. You're saying that in designing libraries you choose not to 
> use `throws` because you don't want to burden your users with `try?` or 
> `try!`, which as you say allows users to handle these errors in any way they 
> choose, even though your functions fail in more than one non-trivial way. 
> This represents a fundamental disagreement with the Swift error handling 
> rationale, and again the disagreement boils down to: are the four letters in 
> `try!` a burden? I would just think of it as making every throwing function 
> at most four letters longer in name.
> 
> Put another way, the Swift error handling design says that at the point of 
> declaration, the choice of `throws` vs. returning nil should be based on how 
> many ways there are to fail (or more accurately, how many meaningfully 
> distinct ways there are to recover from failure), not how often the user 
> cares about that information. If there are two meaningfully distinct ways to 
> recover from failure in your function, but users will likely choose to 
> recover from both failures in the same way 99.9% of the time, still choose 
> `throws`. If there is only one way to recover, choose to return nil. If there 
> are none, choose to trap.
> 
> Put another way, going back to your original statement of motivation:
> 
> There are some cases where it would be nice to throw errors, but errors are 
> rarely expected in most use cases, so the overhead of ‘try’, etc… would make 
> things unusable.
> 
> I disagree with this statement. The overhead of `try` essentially never tips 
> the balance between unusable and usable, for the same reason that making a 
> function name three or four letters longer essentially never tips the balance 
> between usable and unusable.
>  
> Thus fatalError or optionals are used instead.
> 
> In the Swift error handling model, the frequency with which a user might have 
> to write `try!` or `try?` should play no role in the author's choice of 
> throwing vs. returning nil vs. fatalError.
>  
> For example, operators like ‘+’ could never throw because adding ’try’ 
> everywhere would make arithmetic unbearable.
> 
> As we discussed above, AFAICT, addition traps for performance reasons, as 
> Swift aspires to be usable for systems programming.
> 
> Even if that weren't the case, it would never throw because there's only one 
> meaningful way in which addition can fail; thus, if anything, it'd be a 
> failable operation. This would probably not be terrible (other than for 
> performance), as nil values could be propagated to the end of any 
> calculation, at which point a user would write `!` or handle the issue in a 
> more sophisticated way.
> 
> (As a digression, for FP values, NaN offers yet another way of signaling an 
> error, which due to IEEE conformance Swift is obliged to keep distinct; 
> however, as can be evidenced by the fact that the NaN payload is pretty much 
> never used, it can be thought of as a counterpart to nil as opposed to Error.)
> 
> And finally, even if an operator function could fail in multiple ways (we're 
> really getting to very hypothetical hypotheticals here), writing `try!` all 
> the time might look silly and non-Swift users might then mock the language, 
> but I dispute the contention that it would make things "unbearable."
> 
> Thanks,
> Jon
> 
> 
> 
>> On Jan 12, 2017, at 5:34 PM, Greg Parker <[email protected] 
>> <mailto:[email protected]>> wrote:
>> 
>> 
>>> On Jan 12, 2017, at 4:46 PM, Xiaodi Wu via swift-evolution 
>>> <[email protected] <mailto:[email protected]>> wrote:
>>> 
>>>> On Thu, Jan 12, 2017 at 6:27 PM, Jonathan Hull <[email protected] 
>>>> <mailto:[email protected]>> wrote:
>>>> 
>>>> Also, ‘try’ is still required to explicitly mark a potential error 
>>>> propagation point, which is what it was designed to do.  You don’t have 
>>>> ‘try’ with the variants because it is by default no longer a propagation 
>>>> point (unless you make it one explicitly with ’try’).
>>> 
>>> If this is quite safe and more convenient, why then shouldn't it be the 
>>> behavior for `throws`? (That is, why not just allow people to call throwing 
>>> functions without `try` and crash if the error isn't caught? It'd be a 
>>> purely additive proposal that's backwards compatible for all currently 
>>> compiling code.)
>> 
>> Swift prefers that potential runtime crash points be visible in the code. 
>> You can ignore a thrown error and crash instead, but the code will say 
>> `try!`. You can force-unwrap an Optional and crash if it is nil, but the 
>> code will say `!`. 
>> 
>> Allowing `try` to be omitted would obscure those crash points from humans 
>> reading the code. It would no longer be possible to read call sites and be 
>> able to distinguish which ones might crash due to an uncaught error.
>> 
>> (There are exceptions to this rule. Ordinary arithmetic and array access are 
>> checked at runtime, and the default syntax is one that may crash.)
>> 
>> 
>> -- 
>> Greg Parker     [email protected] <mailto:[email protected]>     Runtime 
>> Wrangler

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to