[swift-evolution] [Pitch] Typed throws

Matthew Johnson matthew at anandabits.com
Mon Feb 27 16:49:59 CST 2017


> On Feb 27, 2017, at 4:20 PM, Dave Abrahams <dabrahams at apple.com> wrote:
> 
> 
> on Mon Feb 27 2017, Matthew Johnson <matthew-AT-anandabits.com <http://matthew-at-anandabits.com/>> wrote:
> 
>>> On Feb 27, 2017, at 10:48 AM, Dave Abrahams <dabrahams at apple.com> wrote:
>>> 
>>> 
>>> on Mon Feb 27 2017, Matthew Johnson <matthew-AT-anandabits.com> wrote:
>>> 
>> 
>>>>> On Feb 27, 2017, at 12:32 AM, Dave Abrahams via swift-evolution <swift-evolution at swift.org> wrote:
>>>>> 
>>>>> 
>>>>>> on Fri Feb 17 2017, Joe Groff <swift-evolution at swift.org> wrote:
>>>>>> 
>>>>>> Experience in other languages like Rust and Haskell that use
>>>>>> Result-based error propagation suggests that a single error type is
>>>>>> adequate, and beneficial in many ways. 
>>>>> 
>>>>> And experience in still others, like C++ and Java, suggests that
>>>>> using static types to restrict the kind of information a function can
>>>>> give you when an error occurs may actually be harmful.
>>>> 
>>>> It seems to me that both can be true.  It is up to the author to
>>>> determine which applies in a given use case.  I really don't think
>>>> Swift should require those who understand where it is beneficial to
>>>> use non-throwing functions that return a Result type if they wish to
>>>> realize the benefits when they are relevant.
>>>> 
>>>> My understanding of Chris's meaning of progressive disclosure is that
>>>> the language should have powerful and general tools at its foundation
>>>> that can be ignored until they are necessary for a particular problem.
>>> 
>>> For what problem is this feature necessary?
>> 
>> I was speaking about progressive disclosure generally.  You are right
>> that typed errors are not *necessary* for any problems.  Of course
>> that can be said about many language features.  Perhaps I should have
>> said "until they add value or clarity”.
>> 
>>> 
>>>> In this case the powerful and general tool is typed errors.  The
>>>> syntactic sugar allowing you to omit a type and therefore throw any
>>>> error (typed as Error) allows users to ignore the more general tool
>>>> when it isn't providing value.
>>>> 
>>>> We should focus on educating users who wish to use this tool about the
>>>> tradeoffs involved and how to think about what error type might be
>>>> appropriate in different use cases rather than introduce an arbitrary
>>>> (i.e. not technical) limitation prohibiting typed errors just because
>>>> they can be badly used.  Understanding the tradeoffs is certainly not
>>>> a beginner topic, but there are plenty of things in Swift that are not
>>>> beginner topics.
>>> 
>>> In my experience, given an opportunity to encode something in the type
>>> system or categorize and annotate things using language constructs, most
>>> users will.  That's usually a great instinct, but not in this case, IMO.
>>> The hard problems of error recovery involve maintaining your program
>>> invariants, but this feature contributes nothing toward that end.  The
>>> one thing it *could* improve is the quality of error reporting to end
>>> users, but in practice that ends up devolving to dynamic lookups due to
>>> the need for localization.  So we truly gain very little from encoding
>>> this feature in the type system.
>> 
>> Error reporting and recovery are precisely where type errors can help.
>> I’ll give an example below.
>> 
>>> 
>>> This particular feature is viral in the same sense as C++ const, so I
>>> predict it will either see widespread use, which IMO would be harmful,
>>> or everyone will learn to avoid it.  Either way, it seems like a bad
>>> investment for the language.
>> 
>> I understand why you might make this comparison but I think there is
>> an important difference.
>> 
>> If I receive a const input I can only give that input to other things
>> that take a const.  It restricts my dependencies (what I can do with
>> the input), or conversely I have to abandon const or cast it away (!)
>> because my dependency isn’t declared as taking const.
>> 
>> With typed errors the situation is a little bit different.  My
>> signature is much more independent from that of my dependencies.
>> 
>> For example, I might have some dependencies which my users would
>> prefer to not be tightly coupled with that happen to throw errors.
>> For example, maybe I depend on an IO library and a parsing library.
>> These dependencies are subject to change in the future.  Rather than
>> let the errors propagate directly I wrap them in `enum MyLIbraryError
>> { case IOError(Error); case ParsingError(Error) }`.
>> 
>> This could provide a meaningful abstraction / grouping that helps the
>> client know what might or might not solve the problem.  
> 
> And it might not.  It might turn out that something you have to call
> throws an error that doesn't fit into either case.  Remember, the things
> you call may be closures or methods supplied to you by your clients.
> Then you have to resort to the equivalent of casting away const.
> 
>> It might be worth exposing the error type to callers giving them an
>> easier way to cover different high-level causes of an error.  In this
>> example, I can perform the grouping even if my dependencies throw
>> untyped errors.
> 
> Lots of things might be worth doing.  I'm saying you need a much more
> compelling case that this *is* worth doing before I'd personally risk
> adding it to the language, when experience and, frankly, plain logic,
> show this kind of feature to have been problematic.
> 
>> Even if this enum is exposed resiliently it still provides a
>> significantly better experience for my users than an untyped error.
>> They know that these are the major categories of error that they
>> should be thinking about when using my library and have an easy way to
>> catch each kind of error.  
> 
> You can give users nearly the same value by simply documenting that
> your library throws MyLibraryError, without any of the risks.
> 
>> They might also decide to automatically retry an operation or not
>> depending on what kind of error occurred.
>> 
>> This kind of wrapping and grouping can obviously be performed without
>> typed errors but IMO is better with typed errors where the types never
>> get out of sync with the code (as documentation can) 
> 
> With resilient enums, of course they do get out of sync.
> 
>> and we have better tool integration.  Also, when working in the same
>> module (or with a non-resilient library error) we get the ability to
>> catch errors exhaustively without needing an “unknown error” clause.
>> This *does not* mean every individual error is handled directly, but
>> that they are meaningfully grouped in a way that allows us to cover
>> each possible grouping.
>> 
>> Any problems this feature has with being viral would be one of user
>> expectations and community culture.  It wouldn’t be a strictly
>> technical virality that sets up a kind of dependency between users of
>> my function and implementation details of my function.
> 
> I'm sorry, I don't see any substantive difference, based on what you've
> written here, between this feature and const.

Let me give it one more shot and then I’ll drop it.  :)

Const is viral because if an API does not declare its arguments const it cannot be used by a caller who has a const argument.  It is required in order to make an API as generally useful as possible.

Typed errors are not viral in this way because no callers are prevented from calling an API regardless of whether it declares error types or just throws Error like we have today.  Pressure to declare error types in your signature in order to make your function as generally useful as possible does not exist.  Each function is free to declare error types or not according to the contract it wishes to expose.

An argument can be made that community expectations might develop that good APIs should declare error types and they could be considered viral in this sense because any API that is simply declared `throws` is dropping type information.  But I think this overstates the case.  The community appears to be very sensitive to the problems that can arise when error types are too concrete, especially across module boundaries.  I think we can learn to use the tool where it works well and to avoid it where it causes problems.

> 
> -- 
> -Dave

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170227/e49d1260/attachment.html>


More information about the swift-evolution mailing list