> On Aug 18, 2017, at 3:28 AM, Charlie Monroe <[email protected]> wrote:
>> On Aug 18, 2017, at 8:27 AM, John McCall via swift-evolution 
>> <[email protected]> wrote:
>>> On Aug 18, 2017, at 12:58 AM, Chris Lattner via swift-evolution 
>>> <[email protected]> wrote:
>>> Splitting this off into its own thread:
>>> 
>>>> On Aug 17, 2017, at 7:39 PM, Matthew Johnson <[email protected]> 
>>>> wrote:
>>>> One related topic that isn’t discussed is type errors.  Many third party 
>>>> libraries use a Result type with typed errors.  Moving to an async / await 
>>>> model without also introducing typed errors into Swift would require 
>>>> giving up something that is highly valued by many Swift developers.  Maybe 
>>>> Swift 5 is the right time to tackle typed errors as well.  I would be 
>>>> happy to help with design and drafting a proposal but would need 
>>>> collaborators on the implementation side.
>>> 
>>> Typed throws is something we need to settle one way or the other, and I 
>>> agree it would be nice to do that in the Swift 5 cycle.
>>> 
>>> For the purposes of this sub-discussion, I think there are three kinds of 
>>> code to think about: 
>>> 1) large scale API like Cocoa which evolve (adding significant 
>>> functionality) over the course of many years and can’t break clients. 
>>> 2) the public API of shared swiftpm packages, whose lifecycle may rise and 
>>> fall - being obsoleted and replaced by better packages if they encounter a 
>>> design problem.  
>>> 3) internal APIs and applications, which are easy to change because the 
>>> implementations and clients of the APIs are owned by the same people.
>>> 
>>> These each have different sorts of concerns, and we hope that something can 
>>> start out as #3 but work its way up the stack gracefully.
>>> 
>>> Here is where I think things stand on it:
>>> - There is consensus that untyped throws is the right thing for a large 
>>> scale API like Cocoa.  NSError is effectively proven here.  Even if typed 
>>> throws is introduced, Apple is unlikely to adopt it in their APIs for this 
>>> reason.
>>> - There is consensus that untyped throws is the right default for people to 
>>> reach for for public package (#2).
>>> - There is consensus that Java and other systems that encourage lists of 
>>> throws error types lead to problematic APIs for a variety of reasons.
>>> - There is disagreement about whether internal APIs (#3) should use it.  It 
>>> seems perfect to be able to write exhaustive catches in this situation, 
>>> since everything in knowable. OTOH, this could encourage abuse of error 
>>> handling in cases where you really should return an enum instead of using 
>>> throws.
>>> - Some people are concerned that introducing typed throws would cause 
>>> people to reach for it instead of using untyped throws for public package 
>>> APIs.
>> 
>> Even for non-public code.  The only practical merit of typed throws I have 
>> ever seen someone demonstrate is that it would let them use contextual 
>> lookup in a throw or catch.  People always say "I'll be able to exhaustively 
>> switch over my errors", and then I ask them to show me where they want to do 
>> that, and they show me something that just logs the error, which of course 
>> does not require typed throws.  Every.  Single.  Time.
> 
> The issue I see here with non-typed errors is that relying on documentation 
> is very error-prone. I'll give an example where I've used exhaustive error 
> catching (but then again, I was generally the only one using exhaustive enum 
> switches when we discussed those). I've made a simple library for reporting 
> purchases to a server. The report needs to be signed using a certificate and 
> there are some validations to be made.
> 
> This generally divides the errors into three logical areas - initialization 
> (e.g. errors when loading the certificate, etc.), validation (when the 
> document doesn't pass validation) and sending (network error, error response 
> from the server, etc.).
> 
> Instead of using a large error enum, I've split this into three enums. At 
> this point, especially for a newcommer to the code, he may not realize which 
> method can throw which of these error enums.
> 
> I've found that the app can take advantage of knowing what's wrong. For 
> example, if some required information is missing e.g. 
> Validation.subjectNameMissing is thrown. In such case the application can 
> inform the user that name is missing and it can offer to open UI to enter 
> this information (in the case of my app, the UI for sending is in the 
> document view, while the mentioned "subject name" information is in 
> Preferences).
> 
> This way I exhaustively switch over the error enums, suggesting to the user 
> solution of the particular problem without dumbing down to a message "Oops, 
> something went wrong, but I have no idea what because this kind of error is 
> not handled.".

Surely you must have a message like that.  You're transmitting over a network, 
so all sorts of things can go wrong that you're not going to explain in detail 
to the user or have specific recoveries for.  I would guess that have a generic 
handler for errors, and it has carefully-considered responses for specific 
failures (validation errors, maybe initialization errors) but a default 
response for others.  Maybe you've put effort into handling more errors 
intelligently, trying to let fewer and fewer things end up with the default 
response — that's great, but it must still be there.

That's one of the keys to my argument here: practically speaking, from the 
perspective of any specific bit of code, there will always be a default 
response, because errors naturally quickly tend towards complexity, far more 
complexity than any client can exhaustively handle.  Typed throws just means 
that error types will all have catch-all cases like MyError.other(Error), which 
mostly seems counter-productive to me.

I totally agree that we could do a lot more for documentation.  I might be able 
to be talked into a language design that expressly acknowledges that it's just 
providing documentation and usability hints and doesn't normally let you avoid 
the need for default cases.  But I don't think there's a compelling need for 
such a feature to land in Swift 5.

> Alternatives I've considered:
> 
> - wrapping all the errors into an "Error" enum which would switch over type 
> of the error, which is not a great solution as in some cases you only throw 
> one type of error

That's interesting.  In what cases do you only throw one type of error?  Does 
it not have a catch-all case?

> - I could throw some error that only contains verbose description of the 
> problem (generally a String), but I don't feel it's the library's job to 
> stringify the error as it can be used for a command-line tools as well

Absolutely.  Using enums is a much better way of structuring errors than using 
a string.

> Perhpas I'm missing something, but dealing with UI and presenting an adequate 
> error dialog to the user can be a challenge in current state of things given 
> that currently, in the error catching, you fallback to the basic Error type 
> and generally don't have a lot of options but to display something like 
> "unknown error" - which is terrible for the user.

Again, it comes down to whether that's ever completely avoidable, and I don't 
think it is.  Good error-handling means doing your best to handle common errors 
well.

John.

> 
>> 
>> Sometimes we then go on to have a conversation about wrapping errors in 
>> other error types, and that can be interesting, but now we're talking about 
>> adding a big, messy feature just to get "safety" guarantees for a fairly 
>> minor need.
>> 
>> Programmers often have an instinct to obsess over error taxonomies that is 
>> very rarely directed at solving any real problem; it is just self-imposed 
>> busy-work.
>> 
>>> - Some people think that while it might be useful in some narrow cases, the 
>>> utility isn’t high enough to justify making the language more complex 
>>> (complexity that would intrude on the APIs of result types, futures, etc)
>>> 
>>> I’m sure there are other points in the discussion that I’m forgetting.
>>> 
>>> One thing that I’m personally very concerned about is in the systems 
>>> programming domain.  Systems code is sort of the classic example of code 
>>> that is low-level enough and finely specified enough that there are lots of 
>>> knowable things, including the failure modes.
>> 
>> Here we are using "systems" to mean "embedded systems and kernels".  And 
>> frankly even a kernel is a large enough system that they don't want to 
>> exhaustively switch over failures; they just want the static guarantees that 
>> go along with a constrained error type.
>> 
>>> Beyond expressivity though, our current model involves boxing thrown values 
>>> into an Error existential, something that forces an implicit memory 
>>> allocation when the value is large.  Unless this is fixed, I’m very 
>>> concerned that we’ll end up with a situation where certain kinds of systems 
>>> code (i.e., that which cares about real time guarantees) will not be able 
>>> to use error handling at all.  
>>> 
>>> JohnMC has some ideas on how to change code generation for ‘throws’ to 
>>> avoid this problem, but I don’t understand his ideas enough to know if they 
>>> are practical and likely to happen or not.
>> 
>> Essentially, you give Error a tagged-pointer representation to allow 
>> payload-less errors on non-generic error types to be allocated globally, and 
>> then you can (1) tell people to not throw errors that require allocation if 
>> it's vital to avoid allocation (just like we would tell them today not to 
>> construct classes or indirect enum cases) and (2) allow a special global 
>> payload-less error to be substituted if error allocation fails.
>> 
>> Of course, we could also say that systems code is required to use a 
>> typed-throws feature that we add down the line for their purposes.  Or just 
>> tell them to not use payloads.  Or force them to constrain their error types 
>> to fit within some given size.  (Note that obsessive error taxonomies tend 
>> to end up with a bunch of indirect enum cases anyway, because they get 
>> recursive, so the allocation problem is very real whatever we do.)
>> 
>> John.
>> _______________________________________________
>> swift-evolution mailing list
>> [email protected]
>> https://lists.swift.org/mailman/listinfo/swift-evolution

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

Reply via email to