> On Feb 23, 2017, at 3:31 PM, Vladimir.S <sva...@gmail.com> wrote: > > Thank you for replies, Matthew. They were very helpful to understand the > proposed solution.
You’re welcome. Happy to help! > > On 23.02.2017 21:04, Matthew Johnson wrote: >> >>> On Feb 23, 2017, at 11:53 AM, Vladimir.S <sva...@gmail.com> 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 >>>>> <antonyzhi...@gmail.com <mailto:antonyzhi...@gmail.com>> 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 >>>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>: >>>>> >>>>> # 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 swift-evolution@swift.org >>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>> >> >> . >> _______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution