> On Feb 23, 2017, at 11:53 AM, Vladimir.S <[email protected]> wrote: > > I'm really sorry to interrupt your discussion, but could someone describe(or > point to some article etc) in two words why we need added complexity of typed > throws(in comparing to use documentation)
Thrown errors already have an implicit type: `Error`. What this proposal does is allow us to provide more specific types. > and *if* the suggested solution will guarantee that some method can throw > only explicitly defined type(s) of exception(s) including any re-thrown > exception? Yes, it handles this. When more than one concrete error type is possible you will need to specify a common supertype or wrap them in an enum. The suggested enhancement around implicit conversion during propagation will make this easier. Until then we will need to manually wrap the errors. I showed a pattern that can be used to do this with a reasonably small syntactic weight in functions that need to convert from one error type to another during propagation. > The thread is really long and I personally was not able to follow it from the > beginning(so I believe the answer can be helpful for others like me). > Thank you(really). > > On 23.02.2017 20:09, Matthew Johnson via swift-evolution wrote: >> >>> On Feb 23, 2017, at 10:58 AM, Anton Zhilin <[email protected] >>> <mailto:[email protected]>> wrote: >>> >>> See some inline response below. >>> Also, have you seen the issue I posted in Proposal thread? There is a way >>> to create an instance of "any" type. >> >> Yes, I saw that. There is no problem with that at all. As I point out in >> the analysis below, rethrowing functions are allowed to throw any error >> they want. They are only limited by *where* they may throw. >> >>> >>> 2017-02-23 3:37 GMT+03:00 Matthew Johnson via swift-evolution >>> <[email protected] <mailto:[email protected]>>: >>> >>> # Analysis of the design of typed throws >>> >>> ## Problem >>> >>> There is a problem with how the proposal specifies `rethrows` for >>> functions that take more than one throwing function. The proposal >>> says that the rethrown type must be a common supertype of the type >>> thrown by all of the functions it accepts. This makes some intuitive >>> sense because this is a necessary bound if the rethrowing function >>> lets errors propegate automatically - the rethrown type must be a >>> supertype of all of the automatically propegated errors. >>> >>> This is not how `rethrows` actually works though. `rethrows` >>> currently allows throwing any error type you want, but only in a >>> catch block that covers a call to an argument that actually does >>> throw and *does not* cover a call to a throwing function that is not >>> an argument. The generalization of this to typed throws is that you >>> can rethrow any type you want to, but only in a catch block that >>> meets this rule. >>> >>> >>> ## Example typed rethrow that should be valid and isn't with this >>> proposal >>> >>> This is a good thing, because for many error types `E` and `F` the >>> only common supertype is `Error`. In a non-generic function it would >>> be possible to create a marker protocol and conform both types and >>> specify that as a common supertype. But in generic code this is not >>> possible. The only common supertype we know about is `Error`. The >>> ability to catch the generic errors and wrap them in a sum type is >>> crucial. >>> >>> I'm going to try to use a somewhat realistic example of a generic >>> function that takes two throwing functions that needs to be valid >>> (and is valid under a direct generalization of the current rules >>> applied by `rethrows`). >>> >>> enum TransformAndAccumulateError<E, F> { >>> case transformError(E) >>> case accumulateError(F) >>> } >>> >>> func transformAndAccumulate<E, F, T, U, V>( >>> _ values: [T], >>> _ seed: V, >>> _ transform: T -> throws(E) U, >>> _ accumulate: throws (V, U) -> V >>> ) rethrows(TransformAndAccumulateError<E, F>) -> V { >>> var accumulator = seed >>> try { >>> for value in values { >>> accumulator = try accumulate(accumulator, transform(value)) >>> } >>> } catch let e as E { >>> throw .transformError(e) >>> } catch let f as F { >>> throw .accumulateError(f) >>> } >>> return accumulator >>> } >>> >>> It doesn't matter to the caller that your error type is not a >>> supertype of `E` and `F`. All that matters is that the caller knows >>> that you don't throw an error if the arguments don't throw (not only >>> if the arguments *could* throw, but that one of the arguments >>> actually *did* throw). This is what rethrows specifies. The type >>> that is thrown is unimportant and allowed to be anything the >>> rethrowing function (`transformAndAccumulate` in this case) wishes. >>> >>> >>> Yes, upcasting is only one way (besides others) to convert to a common >>> error type. That's what I had in mind, but I'll state it more explicitly. >> >> The important point is that if you include `rethrows` it should not place >> any restrictions on the type that it throws when its arguments throw. All >> it does is prevent the function from throwing unless there is a dynamic >> guarantee that one of the arguments did in fact throw (which of course >> means if none of them can throw then the rethrowing function cannot throw >> either). >> >>> >>> >>> ## Eliminating rethrows >>> >>> We have discussed eliminating `rethrows` in favor of saying that >>> non-throwing functions have an implicit error type of `Never`. As >>> you can see by the rules above, if the arguments provided have an >>> error type of `Never` the catch blocks are unreachable so we know >>> that the function does not throw. Unfortunately a definition of >>> nonthrowing functions as functions with an error type of `Never` >>> turns out to be too narrow. >>> >>> If you look at the previous example you will see that the only way to >>> propegate error type information in a generic function that rethrows >>> errors from two arguments with unconstrained error types is to catch >>> the errors and wrap them with an enum. Now imagine both arguments >>> happen to be non-throwing (i.e. they throw `Never`). When we wrap >>> the two possible thrown values `Never` we get a type of >>> `TransformAndAccumulateError<Never, Never>`. This type is >>> uninhabitable, but is quite obviously not `Never`. >>> >>> In this proposal we need to specify what qualifies as a non-throwing >>> function. I think we should specifty this in the way that allows us >>> to eliminate `rethrows` from the language. In order to eliminate >>> `rethrows` we need to say that any function throwing an error type >>> that is uninhabitable is non-throwing. I suggest making this change >>> in the proposal. >>> >>> If we specify that any function that throws an uninhabitable type is >>> a non-throwing function then we don't need rethrows. Functions >>> declared without `throws` still get the implicit error type of >>> `Never` but other uninhabitable error types are also considered >>> non-throwing. This provides the same guarantee as `rethrows` does >>> today: if a function simply propegates the errors of its arguments >>> (implicitly or by manual wrapping) and all arguments have `Never` as >>> their error type the function is able to preserve the uninhabitable >>> nature of the wrapped errors and is therefore known to not throw. >>> >>> >>> Yes, any empty type should be allowed instead of just `Never`. That's a >>> general solution to the ploblem with `rethrows` and multiple throwing >>> parameters. >> >> It looks like you clipped out the section "Why this solution is better” >> which showed how `rethrows` is not capable of correctly typing a function >> as non-throwing if it dynamically handles all of the errors thrown by its >> arguments. What do you think of that? In my opinion, it makes a strong >> case for eliminating rethrows and introducing the uninhabited type solution >> from the beginning. >> >>> >>> ### Language support >>> >>> This appears to be a problem in search of a language solution. We >>> need a way to transform one error type into another error type when >>> they do not have a common supertype without cluttering our code and >>> writing boilerplate propegation functions. Ideally all we would need >>> to do is declare the appropriate converting initializers and >>> everything would fall into place. >>> >>> One major motivating reason for making error conversion more >>> ergonomic is that we want to discourage users from simply propegating >>> an error type thrown by a dependency. We want to encourage careful >>> consideration of the type that is exposed whether that be `Error` or >>> something more specific. If conversion is cumbersome many people who >>> want to use typed errors will resort to just exposing the error type >>> of the dependency. >>> >>> The problem of converting one type to another unrelated type (i.e. >>> without a supertype relationship) is a general one. It would be nice >>> if the syntactic solution was general such that it could be taken >>> advantage of in other contexts should we ever have other uses for >>> implicit non-supertype conversions. >>> >>> The most immediate solution that comes to mind is to have a special >>> initializer attribute `@implicit init(_ other: Other)`. A type would >>> provide one implicit initializer for each implicit conversion it >>> supports. We also allow enum cases to be declared `@implicit`. This >>> makes the propegation in the previous example as simple as adding the >>> `@implicit ` attribute to the cases of our enum: >>> >>> enum TransformAndAccumulateError<E, F> { >>> @implicit case transformError(E) >>> @implicit case accumulateError(F) >>> } >>> >>> It is important to note that these implicit conversions *would not* >>> be in effect throughout the program. They would only be used in very >>> specific semantic contexts, the first of which would be error >>> propegation. >>> >>> An error propegation mechanism like this is additive to the original >>> proposal so it could be introduced later. However, if we believe >>> that simply passing on the error type of a dependency is often an >>> anti-pattern and it should be discouraged, it is a good idea to >>> strongly consider introducing this feature along with the intial >>> proposal. >>> >>> >>> Will add to Future work section. >> >> >> >> _______________________________________________ >> 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
