> 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

Reply via email to