> On Aug 18, 2017, at 6:29 PM, Xiaodi Wu <xiaodi...@gmail.com> wrote:
> 
> On Fri, Aug 18, 2017 at 6:19 PM, Matthew Johnson <matt...@anandabits.com 
> <mailto:matt...@anandabits.com>> wrote:
> 
>> On Aug 18, 2017, at 6:15 PM, Xiaodi Wu <xiaodi...@gmail.com 
>> <mailto:xiaodi...@gmail.com>> wrote:
>> 
>> On Fri, Aug 18, 2017 at 09:20 Matthew Johnson via swift-evolution 
>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>> 
>> 
>> Sent from my iPad
>> 
>> On Aug 18, 2017, at 1:27 AM, John McCall <rjmcc...@apple.com 
>> <mailto:rjmcc...@apple.com>> 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.
>> 
>> I agree that exhaustive switching over errors is something that people are 
>> extremely likely to actually want to do.  I also think it's a bit of a red 
>> herring.  The value of typed errors is *not* in exhaustive switching.  It is 
>> in categorization and verified documentation.
>> 
>> Here is a concrete example that applies to almost every app.  When you make 
>> a network request there are many things that could go wrong to which you may 
>> want to respond differently:
>> * There might be no network available.  You might recover by updating the UI 
>> to indicate that and start monitoring for a reachability change.
>> * There might have been a server error that should eventually be resolved 
>> (500).  You might update the UI and provide the user the ability to retry.
>> * There might have been an unrecoverable server error (404).  You will 
>> update the UI.
>> * There might have been a low level parsing error (bad JSON, etc).  Recovery 
>> is perhaps similar in nature to #2, but the problem is less likely to be 
>> resolved quickly so you may not provide a retry option.  You might also want 
>> to do something to notify your dev team that the server is returning JSON 
>> that can't be parsed.
>> * There might have been a higher-level parsing error (converting JSON to 
>> model types).  This might be treated the same as bad JSON.  On the other 
>> hand, depending on the specifics of the app, you might take an alternate 
>> path that only parses the most essential model data in hopes that the 
>> problem was somewhere else and this parse will succeed.
>> 
>> All of this can obviously be accomplished with untyped errors.  That said, 
>> using types to categorize errors would significantly improve the clarity of 
>> such code.  More importantly, I believe that by categorizing errors in ways 
>> that are most relevant to a specific domain a library (perhaps internal to 
>> an app) can encourage developers to think carefully about how to respond.
>> 
>> I used to be rather in favor of adding typed errors, thinking that it can 
>> only benefit and seemed reasonable. However, given the very interesting 
>> discussion here, I'm inclined to think that what you articulate above is 
>> actually a very good argument _against_ adding typed errors.
>> 
>> If I may simplify, the gist of the argument advanced by Tino, Charlie, and 
>> you is that the primary goal is documentation, and that documentation in the 
>> form of prose is insufficient because it can be unreliable. Therefore, you 
>> want a way for the compiler to enforce said documentation. (The 
>> categorization use case, I think, is well addressed by the protocol-based 
>> design discussed already in this thread.)
> 
> Actually documentation is only one of the goals I have and it is the least 
> important.  Please see my subsequent reply to John where I articulate the 
> four primary goals I have for improved error handling, whether it be typed 
> errors or some other mechanism.  I am curious to see what you think of the 
> goals, as well as what mechanism might best address those goals.
> 
> Your other three goals have to do with what you term categorization, unless I 
> misunderstand. Are those not adequately addressed by Joe Groff's 
> protocol-based design?

Can you elaborate on what you mean by Joe Gross’s protocol-based design?  I 
certainly haven’t seen anything that I believe addresses those goals well.

>  
>> 
>> However, the compiler itself cannot reward, only punish in the form of 
>> errors or warnings; if exhaustive switching is a red herring and the payoff 
>> for typed errors is correct documentation, the effectiveness of this kind of 
>> compiler enforcement must be directly proportional to the degree of 
>> extrinsic punishment inflicted by the compiler (since the intrinsic reward 
>> of correct documentation is the same whether it's spelled using doc comments 
>> or the type system). This seems like a heavy-handed way to enforce 
>> documentation of only one specific aspect of a throwing function; moreover, 
>> if this use case were to be sufficiently compelling, then it's certainly a 
>> better argument for SourceKit (or some other builtin tool) to automatically 
>> generate information on all errors thrown than for the compiler to require 
>> that users declare it themselves--even if opt-in.
>> 
>> 
>> Bad error handling is pervasive.  The fact that everyone shows you code that 
>> just logs the error is a prime example of this.  It should be considered a 
>> symptom of a problem, not an acceptable status quo to be maintained.  We 
>> need all the tools at our disposal to encourage better thinking about and 
>> handling of errors.  Most importantly, I think we need a middle ground 
>> between completely untyped errors and an exhaustive list of every possible 
>> error that might happen.  I believe a well designed mechanism for 
>> categorizing errors in a compiler-verified way can do exactly this.
>> 
>> In many respects, there are similarities to this in the design of `NSError` 
>> which provides categorization via the error domain.  This categorization is 
>> a bit more broad than I think is useful in many cases, but it is the best 
>> example I'm aware of.
>> 
>> The primary difference between error domains and the kind of categorization 
>> I am proposing is that error domains categorize based on the source of an 
>> error whereas I am proposing categorization driven by likely recovery 
>> strategies.  Recovery is obviously application dependent, but I think the 
>> example above demonstrates that there are some useful generalizations that 
>> can be made (especially in an app-specific library), even if they don't 
>> apply everywhere.
>> 
>> > 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.
>> 
>> I think you're right that wrapping errors is tightly related to an effective 
>> use of typed errors.  You can do a reasonable job without language support 
>> (as has been discussed on the list in the past).  On the other hand, if 
>> we're going to introduce typed errors we should do it in a way that 
>> *encourages* effective use of them.  My opinion is that encouraging effect 
>> use means categorizing (wrapping) errors without requiring any additional 
>> syntax beyond the simple `try` used by untyped errors.  In practice, this 
>> means we should not need to catch and rethrow an error if all we want to do 
>> is categorize it.  Rust provides good prior art in this area.
>> 
>> >
>> > 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.
>> 
>> I agree that obsessing over intricate taxonomies is counter-productive and 
>> should be discouraged.  On the other hand, I hope the example I provided 
>> above can help to focus the discussion on a practical use of types to 
>> categorize errors in a way that helps guide *thinking* and therefore 
>> improves error handling in practice.
>> 
>> >
>> >> - 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 
>> <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