> On Aug 18, 2017, at 6:15 PM, Xiaodi Wu <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. > > 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