On 28.02.2017 19:48, Matthew Johnson wrote:

On Feb 28, 2017, at 6:47 AM, Vladimir.S <[email protected]> wrote:

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
<[email protected] <mailto:[email protected]>> 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)

Is the question about the ability to create `fullErrorStackDescription`?  Yes, 
that would certainly be possible to implement in an extension.

I’m not sure why your examples use `rethrow` to rethrow the error instead of 
`throw`.  Was that a mistake?

I also want to reiterate that deciding when and how to wrap errors is something 
that requires careful thought and judgment.  The goal is to provide a stable 
and ergonomic way for callers to learn about and handle errors they are likely 
to care about.  There will often be an “everything else” case and that’s ok.

As others have pointed out, the important thing is that it is easy to identify 
cases where I can *improve* UX by recovery or specific messaging to the user.  
The fact that there will usually be a default path is a given, but it’s best to 
avoid that path if possible.


Well, I was trying to figure out how the some code could looks like with typed throws, taking into account your suggestion about built-in underlyingError and originalError props in Error protocol and the idea of "Maybe the solution is to better support wrapping errors by focusing on the problems that wrapping causes". As for 'rethrow' it is not a mistake, but hypothetical use of the keyword to rethrow underlying error inside own error without boilerplate code, i.e. "better support wrapping", compare:

catch let e {
  // need to somehow inject the current e instance into our error instance
  // (or with some other syntax)
  throw BarError.fooRelatedError(SomeType1(), underlying: e)
}

and

catch {
// 'rethrow' can clearly say that the current error instance will be injected
  // into our own error instance in underlyingError prop. we focuse only
  // on our own error instance
  rethrow fooRelatedError(SomeType1())
}

Sorry for not clarifying all this in first message.


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
<[email protected] <mailto:[email protected]>> wrote:


On Mon, 27 Feb 2017 at 8:44 Dave Abrahams via swift-evolution
<[email protected] <mailto:[email protected]>> wrote:


   on Fri Feb 17 2017, Joe Groff <[email protected]
   <mailto:[email protected]>> 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
   [email protected] <mailto:[email protected]>
   https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
[email protected] <mailto:[email protected]>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
[email protected] <mailto:[email protected]>
https://lists.swift.org/mailman/listinfo/swift-evolution



_______________________________________________
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