> On Dec 21, 2015, at 3:00 PM, David Owens II <da...@owensd.io> wrote:
> 
> 
>> I understand that Rust is not doing implicit conversions, but the effect for 
>> the user is pretty much the same.  The try macro is converting the 
>> underlying error to the type that can be propagated.  As I stated, Swift is 
>> not Rust and deserves a different solution.  
>> 
>> Nevertheless, that does not minimize the need to solve the problem.  I 
>> maintain that the problem solved by the try macro is a significant one that 
>> is not addressed by the current proposal.  I would really like to see it 
>> addressed one way or another.
>> 
>>> 
>>> You could make it “nicer” by doing something like this:
>>> 
>>> try MyError.convertFrom(try 
>>> funcThatThrowsAnErrorThatMustBeTranslatedItoMyPublishedError())
>> 
>> Can you elaborate on how you think this would work?  If 
>> funcThatThrowsAnErrorThatMustBeTranslatedItoMyPublishedError actually throws 
>> it will be propagated to the next enclosing catch clause.  
>> MyError.convertFrom will not have a chance to do anything with it.
> 
> Here’s a full playground example (I’ve annotated in comments where the type 
> of error could be described):
> 
> enum InternalError: ErrorType {
>     case Internal(value: Int)
> }
> 
> enum PublishedError: ErrorType {
>     static func from<T>(@autoclosure fn: () throws -> T) throws -> T {
>         do {
>             return try fn()
>         }
>         catch InternalError.Internal(let value) {
>             throw PublishedError.Converted(value: value)
>         }
>         catch {
>             fatalError("unsupported conversion")
>         }
>     }
>     
>     case Converted(value: Int)
> }
> 
> 
> func example() {
> 
>     func bad(value: Int) throws /* InternalError */ -> Int {
>         if value % 2 == 0 { throw InternalError.Internal(value: value) }
>         return value
>     }
> 
>     func verbose(value: Int) throws /* PublishedError */ -> Int {
>         do {
>             return try bad(value)
>         }
>         catch InternalError.Internal(let value) {
>             throw PublishedError.Converted(value: value)
>         }
>         catch {
>             fatalError("unsupported conversion")
>         }
>     }
>     
>     func convert(value: Int) throws /* PublishedError */ -> Int {
>         return try PublishedError.from(try bad(value))
>     }
>     
>     do {
>         let r1 = try verbose(11)
>         print("verbose: \(r1)")
>         
>         let r2 = try convert(9)
>         print("converted: \(r2)")
>     }
>     catch {
>         print("error: \(error)")
>     }
> 
> }
> 
> example()
> 
> 
> As you can see, the “verbose()” and the “from()” conversion are basically the 
> same implementation. What I’m saying is that I believe you can simply do the 
> explicit conversion yourself without much fanfare (compare the verbose() and 
> convert() implementations).
> 
> In the implementation of PublishedError.from() you can use Swift’s pattern 
> matching to do all of your conversions in a single place. Note that where the 
> implementation of “from” is at doesn’t matter, it could be on another type or 
> a free function, whatever.

That is a pretty clever use of @autoclosure!  It can be made even be made even 
more concise with typed errors and a top level conversion function:

@protocol ErrorTypeConvertible {
        // implementations will have to include a default clause which is 
either going to call fatalError 
        // or be an ‘UnknownError’ case in the enum
        init(underlyingError: ErrorType) { … }
        // or
        init<E: ErrorType>(underlyingError: E) { … } 
}

func from<T/*, Internal, Published: ErrorTypeConvertible*/>(@autoclosure fn: () 
throws /* Internal */ -> T) throws /* Published */ -> T {
    do {
        return try fn()
    }
    catch let error as Internal {
        return Published(underlyingError: error)
    }
   // hopefully the compiler is able to detect that this is 
}

    func convert(value: Int) throws /* PublishedError */ -> Int {
        return try from(try bad(value))
    }

This addresses my largest concern which is cluttering up the control flow.  The 
additional noise of an extra ‘try' and a call to ‘from’ isn’t great, but it is 
tolerable, at least initially (I think we would eventually learn that it is 
just noise and get rid of it).  

Unfortunately, I don’t see a way to make it safe.  You had to use fatalError in 
a default case to make it work.  An alternative would have been to include an 
‘UnknownError’ case in ‘PublishedError’.  Neither is not an acceptable solution 
IMO.

If you can make PublishedError.from safe without requiring an ‘UnknownError’ 
case it will also be possible to make a top-level ‘from’ safe.  That would be 
acceptable, but I don’t believe it’s possible in the current language and I’m 
not aware of any proposed changes that would make it possible.

This top level `from` example also brings up a couple of points that I don’t 
recall being addressed in your proposal.  Specifically, the interaction of 
typed errors with generics and type inferrence.  The obvious thing to do is 
allow them to behave the same as any other part of the return type.  If that is 
what you expect and it is not already stated, you should update the proposal to 
specify that.  If you expect something different you definitely need to specify 
what that is.  An implementer will need to know how this should be handled.

I still consider this to be an unresolved concern.  I would like to have a safe 
way to perform error conversion during propagation without cluttering up my 
control flow and seriously degrading readability.  This is a problem that can 
and has been solved in other languages.  IMO it is should be considered an 
essential element of a proposal introducing typed errors.

Matthew

> 
>> Are you willing to explore adding *explicit* syntax to convert thrown errors 
>> to your proposal?  That seems like it might be a reasonable compromise 
>> between implicit conversions and manual boilerplate.  
> 
> The only boiler plate I’m seeing is the explicit conversion call: 
> PublishedError.from(try bad(value))
> 
> Am I misunderstanding something? 
> 
> To me, this would be much more confusing:
> 
>     func convert(value: Int) throws /* PublishedError */ -> Int {
>         return try bad(value)     /* implicit conversion from InternalError 
> -> PublishedError */
>     }
> 
> If there were implicit type conversions, this would have to be something that 
> Swift supported all up. I’d be very hesitant to make this work for only 
> errors. For example, how does implicit conversion work if we can later extend 
> this to async behaviors? Do we have special conversions that can take an 
> async error make it a synchronous error? How about vice-versa?
> 
> I guess I wouldn’t want to go further than having explicit conversions until 
> we better understood all of those answers and how implicit type conversion 
> would work in Swift generally. If I recall, Swift had implicit type 
> conversion in the early versions, and it has been removed in most places. 
> 
> -David
> 

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to