[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