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
> <[email protected]> a écrit :
>
>> On Aug 18, 2017, at 3:28 AM, Charlie Monroe <[email protected]
>> <mailto:[email protected]>> wrote:
>>> On Aug 18, 2017, at 8:27 AM, John McCall via swift-evolution
>>> <[email protected] <mailto:[email protected]>> wrote:
>>>> On Aug 18, 2017, at 12:58 AM, Chris Lattner via swift-evolution
>>>> <[email protected] <mailto:[email protected]>> wrote:
>>>> Splitting this off into its own thread:
>>>>
>>>>> On Aug 17, 2017, at 7:39 PM, Matthew Johnson <[email protected]
>>>>> <mailto:[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] <mailto:[email protected]>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>
> _______________________________________________
> 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