Hello all,

I'm also on the "side" of untyped errors, but I can imagine how other 
developers may like a stricter error hierarchy. It surely fits some situations.

Enter Result<T> and Result<T, E>:

Since Swift "native" errors don't fit well with asynchronous APIs, various ways 
to encapsulate them have emerged, most of them eventually relying on some kind 
of variant of those `Result` type:

        // Untyped errors
        enum Result<T> {
                case success(T)
                case failure(Error)
        }
        
        // Typed errors
        enum Result<T, E: Error> {
                case success(T)
                case failure(E)
        }

The first Result<T> fits well people who like untyped errors. And Result<T, E> 
fits people who prefer typed errors. Result<T> is objectively closer to the 
"spirit" of Swift 2-4. Yet Result<T, E> has the right to live as well.

When Swift 5 brings sugar syntax around async/await/etc, most needs for 
Result<T> will naturally vanish.

However, the need for Result<T, E> will remain. The debate about "typed 
throws", for me, sums up to this question: will the typed folks be able to take 
profit from the syntax sugar brought by async/await/etc of Swift 5? Or will 
they have to keep on carrying Result<T, E> with them?

Gwendal Roué



> Le 18 août 2017 à 10:23, John McCall via swift-evolution 
> <swift-evolution@swift.org> a écrit :
> 
>> On Aug 18, 2017, at 3:28 AM, Charlie Monroe <char...@charliemonroe.net 
>> <mailto:char...@charliemonroe.net>> wrote:
>>> On Aug 18, 2017, at 8:27 AM, John McCall via swift-evolution 
>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>>>> On Aug 18, 2017, at 12:58 AM, Chris Lattner via swift-evolution 
>>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>>>> Splitting this off into its own thread:
>>>> 
>>>>> On Aug 17, 2017, at 7:39 PM, Matthew Johnson <matt...@anandabits.com 
>>>>> <mailto:matt...@anandabits.com>> 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
>>> 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
> https://lists.swift.org/mailman/listinfo/swift-evolution

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

Reply via email to