[swift-evolution] [Pitch] Typed throws

Vladimir.S svabox at gmail.com
Tue Feb 28 06:47:01 CST 2017


On 28.02.2017 0:40, Matthew Johnson via swift-evolution wrote:
>
>> On Feb 27, 2017, at 1:46 PM, David Waite via swift-evolution
>> <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>
>> IMHO, there are two kinds of responses to errors - a specific response,
>> and a general one. Only the calling code knows how it will deal with
>> errors, so a “typed throws” is the function guessing possible calling
>> code behavior.
>>
>> The problem is, that gives four possible combinations - two where the
>> function guesses correctly, and two where it doesn’t. The most damaging
>> is when it suspects a caller doesn’t care about the error, when the
>> caller actually does. This is unwanted wrapping.
>>
>> To provide an example, imagine a library that parses JSON. It has several
>> errors indicating JSON syntactic errors, and an “other” for representing
>> errors on the input stream. It wraps the input stream errors so that it
>> can provide a closed set of errors to the caller.
>>
>> The caller is responsible for returning a data set. It doesn’t think that
>> code calling ‘it” cares about JSON syntactic errors, merely that the
>> object was not able to be restored. It returns its own wrapped error.
>>
>> However, the original caller knows it is loading from disk. If the
>> problem is due to an issue such as access permissions, It has to know
>> implementation details of the API it called if it wishes to dive through
>> the wrapped errors to find out if the problem was filesystem related.
>>
>> Add more layers, and it can be very mysterious why a call failed. Java at
>> least captures stack traces in this case to aid in technical support in
>> diagnosing the error.
>>
>> Wrapping exceptions also prevents an aggregate of errors from different
>> subsystems being handled as a category, such as having a catch block
>> handle RecoverableError generically
>
> Fwiw, I think wrapping errors is something that people are sometimes going
> to want to do regardless of whether they are typed or not.  Maybe the
> solution is to better support wrapping errors by focusing on the problems
> that wrapping causes.  For example, we could do something like this to make

Just to clarify, do you think about something like this? :
(pseudocode, sorry for mistakes)

func foo() throws {}

func bar() throws {}

enum BazError: Error { case baz1, case baz2 }
func baz() throws(BazError) {..}

enum BatError: Error {
   case fooRelatedError(SomeType1)
   case barOrBazRelatedError
   case specialErrorOne(Int)
   case specialErrorTwo(String)
}

func bat() throws(BatError) {
   do {
     try foo()
   }
   catch {
     // underlyingError will be injected
     rethrow .fooRelatedError(SomeType1())
   }

   do {
     try bar()
     try baz()
   }
   catch {
     // underlyingError will be injected
     rethrow .barOrBazRelatedError
   }

   ..
   if flag1 { throw .specialErrorOne(intValue) }
   ..
   if flag2 { throw .specialErrorTwo(stringValue) }
   ..
}

and then

func test() {
   do {
     ...
     try bat()
     ...
   }
   catch let e as BatError {
     switch e {
       case fooRelatedError(let some) : { print(some, e.underlyingError) }
       case barOrBazRelatedError : {
		print("something with bar or baz, so try this:..")

		if let bazError = e.underlyingError as BazError {
			switch bazError {.....}
		} else {
			// do something about "bar" error
		}
	}
       case specialErrorOne(let i) : { print(i) }
       case specialErrorTwo(let s) : { print(s) }
     }

     log(e.fullErrorStackDescription) // BatError.description + 
underlyingError.description + underlyingError.underlyingError.description etc
   }...
}

?

> it easier to get at the original error:
>
> protocol Error {
>   // The error directly underlying this error.
>   // Ideally the compiler would synthesize an implementation for enums
> conforming to `Error`
>   // If `self` is a case that has an associate value which is or conforms
> to `Error` that error would be returned, otherwise `nil` would be returned.
>   var underlyingError: Error? { get }
>
>   // The original error underlying *all* layers of wrapping.
>   // If underlyingError is non-nil this is also non-nil.
>   var originalError: Error { get }
> }
> extension Error {
>     var underlyingError: Error? {
>       return nil
>     }
>     var originalError: Error {
>       return underlyingError?.originalError ?? underlyingError ?? self
>     }
> }
>
> We could even provide syntactic sugar for catch sites that want to deal
> with the original error rather than the wrapped error if that is an
> important use case.
>
>>
>> An interesting solution that has emerged in Ruby to keep library authors
>> from wrapping exceptions is by decorating the existing exception.
>> Exceptions are caught via pattern matching (same as in Swift), so rather
>> than wrap an extension, they extend the error instance with a
>> library-specific module (e.g. swift protocol). So while the error may be
>> a IOError in ruby, you can still catch it via ‘rescue JSONError’
>>
>> Trying to specify the exact errors becomes even more destructive with
>> protocols and closures, where the person defining the interface knows
>> neither which errors the implementor of the call will throw, nor
>> necessarily if the caller will want to implement specific behavior on
>> those errors. This in my personal Java coding experience almost always
>> leads to wrapping in some protocol-specific Exception type which exposes
>> minimal information to the caller, or exposing your errors in some
>> unrelated type like IOException which was declared based on the author’s
>> experience of possible exceptions.
>>
>> -DW
>>
>>> On Feb 27, 2017, at 5:19 AM, Daniel Leping via swift-evolution
>>> <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>
>>>
>>> On Mon, 27 Feb 2017 at 8:44 Dave Abrahams via swift-evolution
>>> <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>
>>>
>>>     on Fri Feb 17 2017, Joe Groff <swift-evolution at swift.org
>>>     <mailto:swift-evolution at swift.org>> wrote:
>>>
>>>     > Experience in other languages like Rust and Haskell that use
>>>     > Result-based error propagation suggests that a single error type is
>>>     > adequate, and beneficial in many ways.
>>>
>>>
>>>
>>>     And experience in still others, like C++ and Java, suggests that
>>>     using static types to restrict the kind of information a function can
>>>     give you when an error occurs may actually be harmful.
>>>
>>> +1 here. It becomes wrapping over wrapping over wrapping. Try doing a
>>> big app in Java (i.e. some kind of layered server) and you'll understand
>>> everything. Ones who tried and still want it - well, there are different
>>> tastes out there.
>>>
>>>
>>>
>>>     --
>>>     -Dave
>>>
>>>     _______________________________________________
>>>     swift-evolution mailing list
>>>     swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>     https://lists.swift.org/mailman/listinfo/swift-evolution
>>>
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>


More information about the swift-evolution mailing list