Remove Failable Initializers

1. Exceptions are used to communicate unrecoverable bugs, like null dereferences, divide-by-zero, etc.

This is really a matter of convention, and also more of a matter of terminology. For example, in Swift, they’re just errors rather than exceptions; i.e- there’s no strict indication of their severity, and like any good “exception” model it’s up to the developer if they want to throw an error onwards, silently hide it, do some recovery etc. If you wish to throw a minor error vs a severe one, then you use different error types and document what they represent.

I’d also argue that a failable initialiser represents no less severe an issue than an error does, as the initialiser has still failed, and therefore created nothing usable, ultimately requiring some form of recovery (testing for nil, supplying a default etc.). It isn’t clear at all to me that these are different things, as both can be recoverable or unrecoverable depending upon which form you use it in (try? vs try! vs try/catch, or Foo() vs Foo()!).

This is emphatically not true. Swift represents unrecoverable bugs with precondition failures, not error throwing. Force unwrapping failures (the Swift equivalent of null dereferences), divide-by-zero, and so on are not failures you can catch in Swift. They cause your code to crash immediately with no hope of reviving it. The Swift compiler even optimizes your code *assuming* these things will crash, so if you were to somehow catch such an error, your app would likely not function correctly. At the very least, it would leak a bunch of objects because it had missed lots of releases.

If you never have before, please read the Error Handling Rationale design document (https://github.com/apple/swift/blob/master/docs/ErrorHandlingRationale.rst\). It's long, but it explains exactly why Swift's error handling is designed the way it is. The "Kinds of error" section might be especially instructive, particularly in that it exists at all. Not all errors are created equal, and many of the ugliest parts of other languages' error systems stem from their failure to recognize that fact.

···

--
Brent Royal-Gordon
Architechies

Saying that the optimizer will *probably* make it okay is a very poor justification for replacing a fast feature with a slow one.

I would like to remind that this proposal amounts to you going into my code, and everyone else's code, and break it.

You would certainly not like me to go into your code and break it, unless I had an excellent reason to do it.

However, so far, I don't see any technical merit to it. It's not faster, it's not shorter, it's not better error handling. And even if you think that you can say that it's not worse, you so far have stopped short of showing that it's better. Can it be faster? Don't think so. Can it be shorter? Don't think so.

This is mostly just enforcing a programming style you believe in to the whole Swift community, without regards to whether the rest agrees with it.

Félix

···

Le 4 mars 2016 à 17:42:06, Haravikk <swift-evolution@haravikk.me> a écrit :

On 4 Mar 2016, at 16:32, Félix Cloutier <felixcca@yahoo.ca <mailto:felixcca@yahoo.ca>> wrote:

If anything, the fact that NSImage doesn't respect the error handling guidelines is an argument in favor of reworking the NSImage interface, not doing away with the guidelines. All (I think?) of the Foundation class initializers accept an inout error parameter, which translate to throws in Swift.

True, but even when you’re writing a type in pure Swift from the start, once you have a failable initialiser it can be easy to just return nil when you think of other possible error conditions that didn’t occur to you sooner (I did this a couple of times myself before try? was added and I switch to error handling only). While someone might do the same with an InvalidArgument error type, as long as it can take a message it’s trivial to customise each unique error that your initialiser/method can produce.

Consider reading Joe Duffy's writeup on error handling in Midori <Joe Duffy - The Error Model; which presents "abandonments" (what I think failable initializers model best) and actual exceptions (what I think throwing models best).

It’s definitely an interesting article but there are a few drawbacks listed to exceptions that I’d like to go over:

1. Exceptions are used to communicate unrecoverable bugs, like null dereferences, divide-by-zero, etc.

This is really a matter of convention, and also more of a matter of terminology. For example, in Swift, they’re just errors rather than exceptions; i.e- there’s no strict indication of their severity, and like any good “exception” model it’s up to the developer if they want to throw an error onwards, silently hide it, do some recovery etc. If you wish to throw a minor error vs a severe one, then you use different error types and document what they represent.

I’d also argue that a failable initialiser represents no less severe an issue than an error does, as the initialiser has still failed, and therefore created nothing usable, ultimately requiring some form of recovery (testing for nil, supplying a default etc.). It isn’t clear at all to me that these are different things, as both can be recoverable or unrecoverable depending upon which form you use it in (try? vs try! vs try/catch, or Foo() vs Foo()!).

3. Although signatures declare exception types, there is no indication at callsites what calls might throw.

This of course isn’t an issue in Swift, as the try keyword is mandatory. Thanks to try? and try! this also eliminates number four (everybody hates exceptions) which is largely a complaint about having to build try/catch blocks around them, but with try? and try! we can easily eliminate that when we know we don’t need any extra information besides whether or not a failure occurred.

Also, regarding overhead of exceptions, I still think that this is something that can be optimised away. All the compiler has to do is identify throw statements, and isolate code specific to them (i.e- variables etc. that are only used as part of a throw); now when you call a method/initialiser with try? or try!, any code identified as being part of the throw can be skipped as all that’s needed is a binary flag (threw vs. succeeded). Of course it sounds a bit easier than it is in reality, but I’m pretty sure it should be possible to change behaviour depending upon how the code is called (i.e- whether an error will actually be used, or simply ignored in favour of its status). When debugging of course the error can be returned in all cases so we can generate and see it.
In short, with Swift’s ability to explicitly declare whether we want an error returned vs simply knowing that an error occurred, it should be possible to optimise such that it is just as efficient as a failable initialiser (if not more so, since the return value isn’t an optional).

1. Exceptions are used to communicate unrecoverable bugs, like null dereferences, divide-by-zero, etc.

This is really a matter of convention, and also more of a matter of terminology. For example, in Swift, they’re just errors rather than exceptions; i.e- there’s no strict indication of their severity, and like any good “exception” model it’s up to the developer if they want to throw an error onwards, silently hide it, do some recovery etc. If you wish to throw a minor error vs a severe one, then you use different error types and document what they represent.

I’d also argue that a failable initialiser represents no less severe an issue than an error does, as the initialiser has still failed, and therefore created nothing usable, ultimately requiring some form of recovery (testing for nil, supplying a default etc.). It isn’t clear at all to me that these are different things, as both can be recoverable or unrecoverable depending upon which form you use it in (try? vs try! vs try/catch, or Foo() vs Foo()!).

This is emphatically not true. Swift represents unrecoverable bugs with precondition failures, not error throwing. Force unwrapping failures (the Swift equivalent of null dereferences), divide-by-zero, and so on are not failures you can catch in Swift.

I think I’ve miscommunicated my meaning; my point is that with error handling you can choose the severity of your error types in terms of whether a caller should handle them or not (though it’s ultimately up to them regardless). They can choose to make any error unrecoverable with try! or an explicit precondition failure too if they like. My point being that if you consider failable initialisers to be somehow less severe than a thrown error, then that’s not really reinforced in any way, as with a throwable initialiser you’re free to throw error types that are minor, e.g- YouProbablyMadeAMistakeError vs SomethingJustExplodedAndPeopleAreDyingError.

There’s nothing that a failable initialiser represents that is distinct from error handling; before error handling was added it was the only option, but now it’s effectively redundant, with less capabilities. Even just the name failable initialiser is a bit of an oddity now as it’s not a failure in the same sense as a precondition failure, i.e- in Swift a failure usually means unrecoverable, which puts a “failable” initialiser on the same footing as any other error.

Saying that the optimizer will *probably* make it okay is a very poor justification for replacing a fast feature with a slow one.

As I pointed out it is possible to optimise error handling to be just as efficient as failable initialisers; if the compiler currently doesn’t do this then it absolutely should irrespective of this proposal. At best this is an argument to delay the proposal until such optimisations can be added, otherwise it’s not really an argument against the proposal itself. In other words, if the performance of try? were identical to a failable initialiser (which it could be) then what really is the difference between the two?

I would like to remind that this proposal amounts to you going into my code, and everyone else's code, and break it.

And other features don't? There are a bunch of potentially code-breaking proposals on this mailing list, but that doesn’t eliminate them from consideration if they are justified. Besides which, Swift (or Xcode) have provided automatic conversion where possible to ease transitions in the past, which wouldn’t be hard to do in this case. For example, the following is the same initialiser in failable and throwable forms:

  init(numericValue:String)? {
    guard isNumeric(numericValue) else { return nil }
    …
  }
  init(numericValue:String) throws {
    guard isNumeric(numericValue) else { throw InvalidParameterError() }
    …
  }

And likewise the following two calls to the respective initialisers:

  let value = Foo(numericValue: number)
  let value = try? Foo(numericValue:number)

It’s a fairly straightforward conversion; make failable initialiser throw instead, replacing return nil with throw of a default error type, add try? at previously failable call sites. Of course a developer could (should) go back and tweak the throwable initialiser to throw a more specific error (e.g- InvalidNumberError or such), but the generic error provides just as much information as returning nil did, but with the added option of catching it to find out exactly which part of the initialiser it was thrown from.

However, so far, I don't see any technical merit to it. It's not faster, it's not shorter, it's not better error handling. And even if you think that you can say that it's not worse, you so far have stopped short of showing that it's better.

The main reason against failable initialisers is that given that the above assignments are identical, this means that it is a redundant feature. Requiring the use of thrown errors is definitely better error handling, as it’s far more flexible, and encourages developers to think about the type of errors should be thrown and/or the contents of these errors (i.e- that InvalidParameterError could easily take a string describing why the parameter was invalid such as “Value cannot contain non-numeric characters”) which gives a lot more information about exactly why something has failed, or it could be replaced with a more specific error type.

Can it be faster?

Possibly, but it can be just as fast, as I pointed out.

Unless you mean faster to use, i.e- how long it takes to type return nil vs throw SomeError(), but in that case it’s arguably better for that to be slower, as it forces more consideration of what type of error to present, rather than a generic “something went wrong but I’m not going to tell you what or where it came from so you’ll have to rely on documentation instead”. Developers may still be lazy and not provide any extra information, but that’s true of most things.

Can it be shorter?

No, but that’s arguably a good thing too; by placing try, try? or try! in front of the initialiser it is much clearer at the call site that an error can occur, and how it is being handled. While the optional type system can catch mistakes with failable initialisers, they’re not that common in my experience, so IMO it makes more sense for it to be explicit when an initialiser can’t be relied upon to return a value. You might argue the same thing with methods and any optional return value, but these are much more common.

···

On 5 Mar 2016, at 01:18, Brent Royal-Gordon <brent@architechies.com <mailto:brent@architechies.com>> wrote:
On 4 Mar 2016, at 23:07, Félix Cloutier <felixcca@yahoo.ca <mailto:felixcca@yahoo.ca>> wrote:

My point being that if you consider failable initialisers to be somehow less severe than a thrown error, then that’s not really reinforced in any way, as with a throwable initialiser you’re free to throw error types that are minor, e.g- YouProbablyMadeAMistakeError vs SomethingJustExplodedAndPeopleAreDyingError.

I don't think it's an issue of severity, but rather an issue of frequency.

Someone asked a similar question on swift-users, and what I said there was that optionals are best used for errors where failure is *expected*—where it is just as likely that an input will fail as that it will succeed, and both cases deserve equal attention. Thrown errors are for cases where failure is still going to happen regularly, but most of the time the operation will succeed and it doesn't make sense to clutter the main flow of control with error handling. Preconditions are for cases where failure should be impossible.

Making a choice between these three categories constrains your API's users. But that is what APIs *do*. Using any API requires you to give up some flexibility and accept the judgement of the API's creator. The decision of `Int.init(_:radix:)` to return an optional instead of throwing an error is no different from its decision to accept Arabic numerals but not Roman numerals. The API's designer has an opinion about what you need and designed it to serve those needs as well as it can; you can either accept that opinion or use something else.

There’s nothing that a failable initialiser represents that is distinct from error handling; before error handling was added it was the only option, but now it’s effectively redundant, with less capabilities.

You see it as having fewer capabilities; I see it as having different use cases. Most code using `Int.init(_:radix:)` is *better* for it being an optional. By returning an optional, the API makes it clear that it can fail, there is only one way for it to fail, and failure is just a routine part of using this API. `throws` says nothing about what can be thrown, and it also suggests that the error cases are unusual and require special handling. That's just not true for this API.

As I pointed out it is possible to optimise error handling to be just as efficient as failable initialisers; if the compiler currently doesn’t do this then it absolutely should irrespective of this proposal.

It is never going to be possible to fully optimize it in the way you imagine.

Ultimately, the reason throwing isn't as efficient as optionals is simply that throwing communicates more data. And it communicates more data precisely *because* it is more expressive, because it supports many error cases instead of just one. Ultimately you're running up against information theory here, and the optimizer can't change fundamental laws of mathematics.

Now, I suppose it would be possible for the compiler to duplicate the function and provide a second implementation for `try?` to use which signals pass/fail in one bit without any details. In private APIs (or internal APIs with testability disabled), it might even be able to optimize away the unused variant. But this is going to make the app bigger and its cache performance worse.

And if it ever *does* have an opportunity to optimize away the full implementation, all that *really* means is that you've wasted programming effort. You took time to carefully model all of the possible errors, but you didn't actually do anything with that information. Or it means you *didn't* do that—you just threw `IDontKnowItBroke` everywhere—and so you used a mechanism that burdens both caller and callee with additional syntax without getting an ounce of benefit from it. Either way, you have wasted programmer time and effort. (Not to mention compiler engineer time and effort to implement this hypothetical optimization.)

However, so far, I don't see any technical merit to it. It's not faster, it's not shorter, it's not better error handling. And even if you think that you can say that it's not worse, you so far have stopped short of showing that it's better.

The main reason against failable initialisers is that given that the above assignments are identical, this means that it is a redundant feature.

All languages have redundant features. `+` is redundant—we have `appendContentsOf` and `addWithOverflow` (this one even provides additional error information!) and all sorts of other things which make `+` unnecessary. And yet we provide it because it would be cumbersome to require those heavyweight mechanisms everywhere.

Requiring the use of thrown errors is definitely better error handling, as it’s far more flexible, and encourages developers to think about the type of errors should be thrown and/or the contents of these errors

As I said, this does not guarantee better error handling if you're just throwing generic `InvalidParameterError`s. And if you *are* taking the time to precisely model all errors, that time may be better spent elsewhere.

Swift goes to great pains to eliminate causes of *incorrect* code. We have optionals because "anything can be nil" causes people to make mistakes; we have trapping arithmetic because people don't design their code to handle overflows; we have designated and convenience initializers because it's really easy to accidentally create mutual recursion between parent and child class initializers.

It is also *incorrect* to ignore the possibility of an error, and so none of the three error handling mechanisms allows you to ignore an error without explicitly choosing to do so.* But providing limited error detail is not incorrect—it is merely making a perfectly acceptable tradeoff.

(* This is not quite true of returning an optional (or boolean) value right now because Swift does not yet make `@warn_unused_result` the default, but it looks like that will change in Swift 3. However, if you *do* capture the result, Swift does force you to reckon with the possibility that it may be optional.)

(i.e- that InvalidParameterError could easily take a string describing why the parameter was invalid such as “Value cannot contain non-numeric characters”)

As an aside, I hope you realize that adding a human-readable string is *not helpful*. Without a machine-readable representation, the error cannot be expressed in domain-specific terms ("The ID number included an 'o'; did you mean '0'?") or even easily localized.

Actually providing a useful error would take a lot of careful modeling; in fact, almost any amount of effort you might put into it may prove insufficient for some particular client. Even if requiring `throws` did succeed in encouraging *more* specific error modeling, that doesn't mean it would provide *enough* error modeling for any particular use case.

And even if we put a magic spell on the Swift compiler to force all programmers using Swift to completely describe errors, *that effort would almost always be wasted*, because most of the time, most of that detail would be ignored.

Completely modeling all sources of error is not always the right solution. At some point, the right solution is to stop answering the "why" questions. We shouldn't punish people for that, even if the stopping point they choose is right at the top.

···

--
Brent Royal-Gordon
Architechies

Just my though, but if this is about redundant feature, why limiting the discussion to initializer. Why not remove Optional as a return type, as any method that return a nil optional on failure may as well throw.

···

Le 5 mars 2016 à 23:33, Haravikk via swift-evolution <swift-evolution@swift.org> a écrit :

On 5 Mar 2016, at 01:18, Brent Royal-Gordon <brent@architechies.com <mailto:brent@architechies.com>> wrote:

1. Exceptions are used to communicate unrecoverable bugs, like null dereferences, divide-by-zero, etc.

This is really a matter of convention, and also more of a matter of terminology. For example, in Swift, they’re just errors rather than exceptions; i.e- there’s no strict indication of their severity, and like any good “exception” model it’s up to the developer if they want to throw an error onwards, silently hide it, do some recovery etc. If you wish to throw a minor error vs a severe one, then you use different error types and document what they represent.

I’d also argue that a failable initialiser represents no less severe an issue than an error does, as the initialiser has still failed, and therefore created nothing usable, ultimately requiring some form of recovery (testing for nil, supplying a default etc.). It isn’t clear at all to me that these are different things, as both can be recoverable or unrecoverable depending upon which form you use it in (try? vs try! vs try/catch, or Foo() vs Foo()!).

This is emphatically not true. Swift represents unrecoverable bugs with precondition failures, not error throwing. Force unwrapping failures (the Swift equivalent of null dereferences), divide-by-zero, and so on are not failures you can catch in Swift.

I think I’ve miscommunicated my meaning; my point is that with error handling you can choose the severity of your error types in terms of whether a caller should handle them or not (though it’s ultimately up to them regardless). They can choose to make any error unrecoverable with try! or an explicit precondition failure too if they like. My point being that if you consider failable initialisers to be somehow less severe than a thrown error, then that’s not really reinforced in any way, as with a throwable initialiser you’re free to throw error types that are minor, e.g- YouProbablyMadeAMistakeError vs SomethingJustExplodedAndPeopleAreDyingError.

There’s nothing that a failable initialiser represents that is distinct from error handling; before error handling was added it was the only option, but now it’s effectively redundant, with less capabilities. Even just the name failable initialiser is a bit of an oddity now as it’s not a failure in the same sense as a precondition failure, i.e- in Swift a failure usually means unrecoverable, which puts a “failable” initialiser on the same footing as any other error.

On 4 Mar 2016, at 23:07, Félix Cloutier <felixcca@yahoo.ca <mailto:felixcca@yahoo.ca>> wrote:
Saying that the optimizer will *probably* make it okay is a very poor justification for replacing a fast feature with a slow one.

As I pointed out it is possible to optimise error handling to be just as efficient as failable initialisers; if the compiler currently doesn’t do this then it absolutely should irrespective of this proposal. At best this is an argument to delay the proposal until such optimisations can be added, otherwise it’s not really an argument against the proposal itself. In other words, if the performance of try? were identical to a failable initialiser (which it could be) then what really is the difference between the two?

I would like to remind that this proposal amounts to you going into my code, and everyone else's code, and break it.

And other features don't? There are a bunch of potentially code-breaking proposals on this mailing list, but that doesn’t eliminate them from consideration if they are justified. Besides which, Swift (or Xcode) have provided automatic conversion where possible to ease transitions in the past, which wouldn’t be hard to do in this case. For example, the following is the same initialiser in failable and throwable forms:

  init(numericValue:String)? {
    guard isNumeric(numericValue) else { return nil }
    …
  }
  init(numericValue:String) throws {
    guard isNumeric(numericValue) else { throw InvalidParameterError() }
    …
  }

And likewise the following two calls to the respective initialisers:

  let value = Foo(numericValue: number)
  let value = try? Foo(numericValue:number)

It’s a fairly straightforward conversion; make failable initialiser throw instead, replacing return nil with throw of a default error type, add try? at previously failable call sites. Of course a developer could (should) go back and tweak the throwable initialiser to throw a more specific error (e.g- InvalidNumberError or such), but the generic error provides just as much information as returning nil did, but with the added option of catching it to find out exactly which part of the initialiser it was thrown from.

However, so far, I don't see any technical merit to it. It's not faster, it's not shorter, it's not better error handling. And even if you think that you can say that it's not worse, you so far have stopped short of showing that it's better.

The main reason against failable initialisers is that given that the above assignments are identical, this means that it is a redundant feature. Requiring the use of thrown errors is definitely better error handling, as it’s far more flexible, and encourages developers to think about the type of errors should be thrown and/or the contents of these errors (i.e- that InvalidParameterError could easily take a string describing why the parameter was invalid such as “Value cannot contain non-numeric characters”) which gives a lot more information about exactly why something has failed, or it could be replaced with a more specific error type.

Can it be faster?

Possibly, but it can be just as fast, as I pointed out.

Unless you mean faster to use, i.e- how long it takes to type return nil vs throw SomeError(), but in that case it’s arguably better for that to be slower, as it forces more consideration of what type of error to present, rather than a generic “something went wrong but I’m not going to tell you what or where it came from so you’ll have to rely on documentation instead”. Developers may still be lazy and not provide any extra information, but that’s true of most things.

Can it be shorter?

No, but that’s arguably a good thing too; by placing try, try? or try! in front of the initialiser it is much clearer at the call site that an error can occur, and how it is being handled. While the optional type system can catch mistakes with failable initialisers, they’re not that common in my experience, so IMO it makes more sense for it to be explicit when an initialiser can’t be relied upon to return a value. You might argue the same thing with methods and any optional return value, but these are much more common.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

It's not just that it communicates more data, but also that it does so
with type erasure.

···

on Sat Mar 05 2016, Brent Royal-Gordon <swift-evolution@swift.org> wrote:

Ultimately, the reason throwing isn't as efficient as optionals is
simply that throwing communicates more data. And it communicates more
data precisely *because* it is more expressive, because it supports
many error cases instead of just one. Ultimately you're running up
against information theory here, and the optimizer can't change
fundamental laws of mathematics.

--
-Dave

Just my though, but if this is about redundant feature, why limiting the discussion to initializer. Why not remove Optional as a return type, as any method that return a nil optional on failure may as well throw.

HUGE Nooooooo, and -1. This is so anti-Swift it hurts Swift's feelings, haha. I find some redundancy very, very useful. Not every problem is best handled with one methodology. Removing does not always mean improving. To remove fail able initializers would be disappointing. Sometimes you just don't need an error. What if the init fails because an instance already exists -- the user is just clicking like mad, haha. Well you don't a screen full of "hey, dude, I'm working on it stop clicking already". You just allow for one click, the initializer does it's thing and the user is happy that system didn't crash from error overload. Perhaps that's a poor example, but it's simple for my simple mind. To remove optional as a return type... Dictionary is broken, so is Set... The language just died. And you killed it. This is not a good idea. It would change a massive amount of code both in the language and in my own projects. Absolutely, no, please.

Myles

···

On Mar 6, 2016, at 4:22 AM, Jean-Daniel Dupas via swift-evolution <swift-evolution@swift.org> wrote:

Le 5 mars 2016 à 23:33, Haravikk via swift-evolution <swift-evolution@swift.org> a écrit :

On 5 Mar 2016, at 01:18, Brent Royal-Gordon <brent@architechies.com> wrote:

1. Exceptions are used to communicate unrecoverable bugs, like null dereferences, divide-by-zero, etc.

This is really a matter of convention, and also more of a matter of terminology. For example, in Swift, they’re just errors rather than exceptions; i.e- there’s no strict indication of their severity, and like any good “exception” model it’s up to the developer if they want to throw an error onwards, silently hide it, do some recovery etc. If you wish to throw a minor error vs a severe one, then you use different error types and document what they represent.

I’d also argue that a failable initialiser represents no less severe an issue than an error does, as the initialiser has still failed, and therefore created nothing usable, ultimately requiring some form of recovery (testing for nil, supplying a default etc.). It isn’t clear at all to me that these are different things, as both can be recoverable or unrecoverable depending upon which form you use it in (try? vs try! vs try/catch, or Foo() vs Foo()!).

This is emphatically not true. Swift represents unrecoverable bugs with precondition failures, not error throwing. Force unwrapping failures (the Swift equivalent of null dereferences), divide-by-zero, and so on are not failures you can catch in Swift.

I think I’ve miscommunicated my meaning; my point is that with error handling you can choose the severity of your error types in terms of whether a caller should handle them or not (though it’s ultimately up to them regardless). They can choose to make any error unrecoverable with try! or an explicit precondition failure too if they like. My point being that if you consider failable initialisers to be somehow less severe than a thrown error, then that’s not really reinforced in any way, as with a throwable initialiser you’re free to throw error types that are minor, e.g- YouProbablyMadeAMistakeError vs SomethingJustExplodedAndPeopleAreDyingError.

There’s nothing that a failable initialiser represents that is distinct from error handling; before error handling was added it was the only option, but now it’s effectively redundant, with less capabilities. Even just the name failable initialiser is a bit of an oddity now as it’s not a failure in the same sense as a precondition failure, i.e- in Swift a failure usually means unrecoverable, which puts a “failable” initialiser on the same footing as any other error.

On 4 Mar 2016, at 23:07, Félix Cloutier <felixcca@yahoo.ca> wrote:
Saying that the optimizer will *probably* make it okay is a very poor justification for replacing a fast feature with a slow one.

As I pointed out it is possible to optimise error handling to be just as efficient as failable initialisers; if the compiler currently doesn’t do this then it absolutely should irrespective of this proposal. At best this is an argument to delay the proposal until such optimisations can be added, otherwise it’s not really an argument against the proposal itself. In other words, if the performance of try? were identical to a failable initialiser (which it could be) then what really is the difference between the two?

I would like to remind that this proposal amounts to you going into my code, and everyone else's code, and break it.

And other features don't? There are a bunch of potentially code-breaking proposals on this mailing list, but that doesn’t eliminate them from consideration if they are justified. Besides which, Swift (or Xcode) have provided automatic conversion where possible to ease transitions in the past, which wouldn’t be hard to do in this case. For example, the following is the same initialiser in failable and throwable forms:

  init(numericValue:String)? {
    guard isNumeric(numericValue) else { return nil }
    …
  }
  init(numericValue:String) throws {
    guard isNumeric(numericValue) else { throw InvalidParameterError() }
    …
  }

And likewise the following two calls to the respective initialisers:

  let value = Foo(numericValue: number)
  let value = try? Foo(numericValue:number)

It’s a fairly straightforward conversion; make failable initialiser throw instead, replacing return nil with throw of a default error type, add try? at previously failable call sites. Of course a developer could (should) go back and tweak the throwable initialiser to throw a more specific error (e.g- InvalidNumberError or such), but the generic error provides just as much information as returning nil did, but with the added option of catching it to find out exactly which part of the initialiser it was thrown from.

However, so far, I don't see any technical merit to it. It's not faster, it's not shorter, it's not better error handling. And even if you think that you can say that it's not worse, you so far have stopped short of showing that it's better.

The main reason against failable initialisers is that given that the above assignments are identical, this means that it is a redundant feature. Requiring the use of thrown errors is definitely better error handling, as it’s far more flexible, and encourages developers to think about the type of errors should be thrown and/or the contents of these errors (i.e- that InvalidParameterError could easily take a string describing why the parameter was invalid such as “Value cannot contain non-numeric characters”) which gives a lot more information about exactly why something has failed, or it could be replaced with a more specific error type.

Can it be faster?

Possibly, but it can be just as fast, as I pointed out.

Unless you mean faster to use, i.e- how long it takes to type return nil vs throw SomeError(), but in that case it’s arguably better for that to be slower, as it forces more consideration of what type of error to present, rather than a generic “something went wrong but I’m not going to tell you what or where it came from so you’ll have to rely on documentation instead”. Developers may still be lazy and not provide any extra information, but that’s true of most things.

Can it be shorter?

No, but that’s arguably a good thing too; by placing try, try? or try! in front of the initialiser it is much clearer at the call site that an error can occur, and how it is being handled. While the optional type system can catch mistakes with failable initialisers, they’re not that common in my experience, so IMO it makes more sense for it to be explicit when an initialiser can’t be relied upon to return a value. You might argue the same thing with methods and any optional return value, but these are much more common.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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

ErrorType does get some special treatment from the compiler and runtime to make throwing errors cheap. ErrorType's representation is specialized to use a single pointer to a refcounted box, so that it can be returned and passed cheaply, and on Darwin platforms it is laid out to be toll-free-bridgeable as an NSError subclass, so that 'as NSError' conversions are free. It's not implemented yet, but on 64-bit platforms we have enough room to pack a small error enum along with its type metadata into the pointer without allocating at all.

-Joe

···

On Mar 5, 2016, at 7:01 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

My point being that if you consider failable initialisers to be somehow less severe than a thrown error, then that’s not really reinforced in any way, as with a throwable initialiser you’re free to throw error types that are minor, e.g- YouProbablyMadeAMistakeError vs SomethingJustExplodedAndPeopleAreDyingError.

I don't think it's an issue of severity, but rather an issue of frequency.

Someone asked a similar question on swift-users, and what I said there was that optionals are best used for errors where failure is *expected*—where it is just as likely that an input will fail as that it will succeed, and both cases deserve equal attention. Thrown errors are for cases where failure is still going to happen regularly, but most of the time the operation will succeed and it doesn't make sense to clutter the main flow of control with error handling. Preconditions are for cases where failure should be impossible.

Making a choice between these three categories constrains your API's users. But that is what APIs *do*. Using any API requires you to give up some flexibility and accept the judgement of the API's creator. The decision of `Int.init(_:radix:)` to return an optional instead of throwing an error is no different from its decision to accept Arabic numerals but not Roman numerals. The API's designer has an opinion about what you need and designed it to serve those needs as well as it can; you can either accept that opinion or use something else.

There’s nothing that a failable initialiser represents that is distinct from error handling; before error handling was added it was the only option, but now it’s effectively redundant, with less capabilities.

You see it as having fewer capabilities; I see it as having different use cases. Most code using `Int.init(_:radix:)` is *better* for it being an optional. By returning an optional, the API makes it clear that it can fail, there is only one way for it to fail, and failure is just a routine part of using this API. `throws` says nothing about what can be thrown, and it also suggests that the error cases are unusual and require special handling. That's just not true for this API.

As I pointed out it is possible to optimise error handling to be just as efficient as failable initialisers; if the compiler currently doesn’t do this then it absolutely should irrespective of this proposal.

It is never going to be possible to fully optimize it in the way you imagine.

Ultimately, the reason throwing isn't as efficient as optionals is simply that throwing communicates more data. And it communicates more data precisely *because* it is more expressive, because it supports many error cases instead of just one. Ultimately you're running up against information theory here, and the optimizer can't change fundamental laws of mathematics.

Now, I suppose it would be possible for the compiler to duplicate the function and provide a second implementation for `try?` to use which signals pass/fail in one bit without any details. In private APIs (or internal APIs with testability disabled), it might even be able to optimize away the unused variant. But this is going to make the app bigger and its cache performance worse.

And if it ever *does* have an opportunity to optimize away the full implementation, all that *really* means is that you've wasted programming effort. You took time to carefully model all of the possible errors, but you didn't actually do anything with that information. Or it means you *didn't* do that—you just threw `IDontKnowItBroke` everywhere—and so you used a mechanism that burdens both caller and callee with additional syntax without getting an ounce of benefit from it. Either way, you have wasted programmer time and effort. (Not to mention compiler engineer time and effort to implement this hypothetical optimization.)

However, so far, I don't see any technical merit to it. It's not faster, it's not shorter, it's not better error handling. And even if you think that you can say that it's not worse, you so far have stopped short of showing that it's better.

The main reason against failable initialisers is that given that the above assignments are identical, this means that it is a redundant feature.

All languages have redundant features. `+` is redundant—we have `appendContentsOf` and `addWithOverflow` (this one even provides additional error information!) and all sorts of other things which make `+` unnecessary. And yet we provide it because it would be cumbersome to require those heavyweight mechanisms everywhere.

Requiring the use of thrown errors is definitely better error handling, as it’s far more flexible, and encourages developers to think about the type of errors should be thrown and/or the contents of these errors

As I said, this does not guarantee better error handling if you're just throwing generic `InvalidParameterError`s. And if you *are* taking the time to precisely model all errors, that time may be better spent elsewhere.

Swift goes to great pains to eliminate causes of *incorrect* code. We have optionals because "anything can be nil" causes people to make mistakes; we have trapping arithmetic because people don't design their code to handle overflows; we have designated and convenience initializers because it's really easy to accidentally create mutual recursion between parent and child class initializers.

It is also *incorrect* to ignore the possibility of an error, and so none of the three error handling mechanisms allows you to ignore an error without explicitly choosing to do so.* But providing limited error detail is not incorrect—it is merely making a perfectly acceptable tradeoff.

(* This is not quite true of returning an optional (or boolean) value right now because Swift does not yet make `@warn_unused_result` the default, but it looks like that will change in Swift 3. However, if you *do* capture the result, Swift does force you to reckon with the possibility that it may be optional.)

(i.e- that InvalidParameterError could easily take a string describing why the parameter was invalid such as “Value cannot contain non-numeric characters”)

As an aside, I hope you realize that adding a human-readable string is *not helpful*. Without a machine-readable representation, the error cannot be expressed in domain-specific terms ("The ID number included an 'o'; did you mean '0'?") or even easily localized.

Actually providing a useful error would take a lot of careful modeling; in fact, almost any amount of effort you might put into it may prove insufficient for some particular client. Even if requiring `throws` did succeed in encouraging *more* specific error modeling, that doesn't mean it would provide *enough* error modeling for any particular use case.

And even if we put a magic spell on the Swift compiler to force all programmers using Swift to completely describe errors, *that effort would almost always be wasted*, because most of the time, most of that detail would be ignored.

Completely modeling all sources of error is not always the right solution. At some point, the right solution is to stop answering the "why" questions. We shouldn't punish people for that, even if the stopping point they choose is right at the top.

Just my though, but if this is about redundant feature, why limiting the discussion to initializer. Why not remove Optional as a return type, as any method that return a nil optional on failure may as well throw.

Because methods and properties are different; for example, the .indexOf() method on a collection can be called with an element that doesn’t exist within the collection, this isn’t an error as the method doesn’t require you to only request elements that can be found, so forcing that to throw would make no sense.

However every case I’ve seen for failable initialisers seems to be modelling some type of actual error (e.g- invalid data) as opposed to input being valid but the result is nothing, so it still seem to me like it should be using error handling, but I think at this point I’ve said more than enough on the subject.

I suppose it would be possible for the compiler to duplicate the function and provide a second implementation for `try?` to use which signals pass/fail in one bit without any details. In private APIs (or internal APIs with testability disabled), it might even be able to optimize away the unused variant. But this is going to make the app bigger and its cache performance worse.

A duplicate shouldn’t be necessary; functions/initialisers that can throw just need to be called with a flag indicating whether they should capture or ignore errors as appropriate for try vs try? and try!. Any statement that is identified as being specific to a throw is then wrapped in a conditional based on this flag so it can be skipped if errors are ignored. The throw itself will then either return the error (if it was generated) otherwise it simply indicates that an error occurred and nothing more. The compiler should already be grouping throw-specific statements together where possible, so that they aren’t visited during a non-error path so this shouldn’t add many of these conditional checks at all (in most cases there should only be one, and it will only be tested in the throw branch).

All languages have redundant features. `+` is redundant—we have `appendContentsOf` and `addWithOverflow` (this one even provides additional error information!) and all sorts of other things which make `+` unnecessary. And yet we provide it because it would be cumbersome to require those heavyweight mechanisms everywhere.

In the case of addWithOverflow that’s not redundant in the same way as its a matter of safety; + implicitly guards against overflow, while addWithOverflow() and &+ explicitly allow it. Regarding operators vs the methods they actually call, that’s sugar; the operator is just a shorter way to do the same thing, i.e- the operator is just a prettier way to write what is exactly the same code.

With the failable initialisers you’re using a distinctly separate feature to achieve the same end result that error handling is capable of, which is where I feel that the overlap produces redundancy.

As I said, this does not guarantee better error handling if you're just throwing generic `InvalidParameterError`s. And if you *are* taking the time to precisely model all errors, that time may be better spent elsewhere.

That’s more an issue of developer laziness more than anything, but at least it’s still communicating that that’s exactly what the error is. If the standard library includes a good set of default errors then that should cover most use-cases; anything that isn’t covered by a standard error meanwhile absolutely should be given a new error type IMO.

As an aside, I hope you realize that adding a human-readable string is *not helpful*. Without a machine-readable representation, the error cannot be expressed in domain-specific terms ("The ID number included an 'o'; did you mean '0'?") or even easily localized.

I’m not sure what you mean by this exactly; if you mean that an error should include extra data that can be extracted from the error instance itself then that depends on the error. If you’re capturing an invalid parameter error then it will be for debugging purposes I think, so the message is for the developer to inform them of which parameter failed (rather than having to have a different error for each parameter) and possibly some info on why, i.e- I’m not talking about text that would be spit out to a user or logged directly (except perhaps as a debug statement), or that you would expect to capture and process extensively. In this case even a simple string should be plenty helpful compared to nothing.

···

On 6 Mar 2016, at 09:22, Jean-Daniel Dupas <mailing@xenonium.com> wrote:
On 6 Mar 2016, at 03:01, Brent Royal-Gordon <brent@architechies.com> wrote:

For me the factory method could still be implemented as a class method that returns an optionalAnd in fact that example used of not creating a duplicate instance would fit here.
I limited this to purely to initialisers as there is a lot more that could get wrong and I felt that in some case falliable initlizers were encouraging people to trivialise something that may bite them in the long run like parsing Json for a model it failing and returning nil. They would have to manually insert breakpoints to figure out why and po the Json.
For normal functions I think there are many acceptable cases where returning an optional instead of an error is okay.
In any case if this is *not* the right approach, I think the upcoming and accepted API design guidelines could be updated from the proposal I am about to submit for this. It would be great to have some guidance of how we should model errors. Right now even after reading the doc I am not sure if throwing an error for my example of parsing Json into a model is correct or if I should just return nil and if I return nil is logging it out to the console a good idea ?
I will add a consideration in my proposal for updating the new API Guidelines with guidedence for error handling. At the very least it will help clear things up.

···

Sent from Supmenow.com

On Sun, Mar 6, 2016 at 7:27 AM -0800, "Myles Schultz via swift-evolution" <swift-evolution@swift.org> wrote:

On Mar 6, 2016, at 4:22 AM, Jean-Daniel Dupas via swift-evolution <swift-evolution@swift.org> wrote:

Just my though, but if this is about redundant feature, why limiting the discussion to initializer. Why not remove Optional as a return type, as any method that return a nil optional on failure may as well throw.
HUGE Nooooooo, and -1. This is so anti-Swift it hurts Swift's feelings, haha. I find some redundancy very, very useful. Not every problem is best handled with one methodology. Removing does not always mean improving. To remove fail able initializers would be disappointing. Sometimes you just don't need an error. What if the init fails because an instance already exists -- the user is just clicking like mad, haha. Well you don't a screen full of "hey, dude, I'm working on it stop clicking already". You just allow for one click, the initializer does it's thing and the user is happy that system didn't crash from error overload. Perhaps that's a poor example, but it's simple for my simple mind. To remove optional as a return type... Dictionary is broken, so is Set... The language just died. And you killed it. This is not a good idea. It would change a massive amount of code both in the language and in my own projects. Absolutely, no, please.
Myles

Le 5 mars 2016 à 23:33, Haravikk via swift-evolution <swift-evolution@swift.org> a écrit :

On 5 Mar 2016, at 01:18, Brent Royal-Gordon <brent@architechies.com> wrote:
1. Exceptions are used to communicate unrecoverable bugs, like null dereferences, divide-by-zero, etc.

This is really a matter of convention, and also more of a matter of terminology. For example, in Swift, they’re just errors rather than exceptions; i.e- there’s no strict indication of their severity, and like any good “exception” model it’s up to the developer if they want to throw an error onwards, silently hide it, do some recovery etc. If you wish to throw a minor error vs a severe one, then you use different error types and document what they represent.

I’d also argue that a failable initialiser represents no less severe an issue than an error does, as the initialiser has still failed, and therefore created nothing usable, ultimately requiring some form of recovery (testing for nil, supplying a default etc.). It isn’t clear at all to me that these are different things, as both can be recoverable or unrecoverable depending upon which form you use it in (try? vs try! vs try/catch, or Foo() vs Foo()!).

This is emphatically not true. Swift represents unrecoverable bugs with precondition failures, not error throwing. Force unwrapping failures (the Swift equivalent of null dereferences), divide-by-zero, and so on are not failures you can catch in Swift.
I think I’ve miscommunicated my meaning; my point is that with error handling you can choose the severity of your error types in terms of whether a caller should handle them or not (though it’s ultimately up to them regardless). They can choose to make any error unrecoverable with try! or an explicit precondition failure too if they like. My point being that if you consider failable initialisers to be somehow less severe than a thrown error, then that’s not really reinforced in any way, as with a throwable initialiser you’re free to throw error types that are minor, e.g- YouProbablyMadeAMistakeError vs SomethingJustExplodedAndPeopleAreDyingError.
There’s nothing that a failable initialiser represents that is distinct from error handling; before error handling was added it was the only option, but now it’s effectively redundant, with less capabilities. Even just the name failable initialiser is a bit of an oddity now as it’s not a failure in the same sense as a precondition failure, i.e- in Swift a failure usually means unrecoverable, which puts a “failable” initialiser on the same footing as any other error.

On 4 Mar 2016, at 23:07, Félix Cloutier <felixcca@yahoo.ca> wrote:Saying that the optimizer will *probably* make it okay is a very poor justification for replacing a fast feature with a slow one.
As I pointed out it is possible to optimise error handling to be just as efficient as failable initialisers; if the compiler currently doesn’t do this then it absolutely should irrespective of this proposal. At best this is an argument to delay the proposal until such optimisations can be added, otherwise it’s not really an argument against the proposal itself. In other words, if the performance of try? were identical to a failable initialiser (which it could be) then what really is the difference between the two?
I would like to remind that this proposal amounts to you going into my code, and everyone else's code, and break it.
And other features don't? There are a bunch of potentially code-breaking proposals on this mailing list, but that doesn’t eliminate them from consideration if they are justified. Besides which, Swift (or Xcode) have provided automatic conversion where possible to ease transitions in the past, which wouldn’t be hard to do in this case. For example, the following is the same initialiser in failable and throwable forms:
  init(numericValue:String)? { guard isNumeric(numericValue) else { return nil } … } init(numericValue:String) throws { guard isNumeric(numericValue) else { throw InvalidParameterError() } … }
And likewise the following two calls to the respective initialisers:
  let value = Foo(numericValue: number) let value = try? Foo(numericValue:number)
It’s a fairly straightforward conversion; make failable initialiser throw instead, replacing return nil with throw of a default error type, add try? at previously failable call sites. Of course a developer could (should) go back and tweak the throwable initialiser to throw a more specific error (e.g- InvalidNumberError or such), but the generic error provides just as much information as returning nil did, but with the added option of catching it to find out exactly which part of the initialiser it was thrown from.
However, so far, I don't see any technical merit to it. It's not faster, it's not shorter, it's not better error handling. And even if you think that you can say that it's not worse, you so far have stopped short of showing that it's better.
The main reason against failable initialisers is that given that the above assignments are identical, this means that it is a redundant feature. Requiring the use of thrown errors is definitely better error handling, as it’s far more flexible, and encourages developers to think about the type of errors should be thrown and/or the contents of these errors (i.e- that InvalidParameterError could easily take a string describing why the parameter was invalid such as “Value cannot contain non-numeric characters”) which gives a lot more information about exactly why something has failed, or it could be replaced with a more specific error type.
Can it be faster?
Possibly, but it can be just as fast, as I pointed out.
Unless you mean faster to use, i.e- how long it takes to type return nil vs throw SomeError(), but in that case it’s arguably better for that to be slower, as it forces more consideration of what type of error to present, rather than a generic “something went wrong but I’m not going to tell you what or where it came from so you’ll have to rely on documentation instead”. Developers may still be lazy and not provide any extra information, but that’s true of most things.
Can it be shorter?
No, but that’s arguably a good thing too; by placing try, try? or try! in front of the initialiser it is much clearer at the call site that an error can occur, and how it is being handled. While the optional type system can catch mistakes with failable initialisers, they’re not that common in my experience, so IMO it makes more sense for it to be explicit when an initialiser can’t be relied upon to return a value. You might argue the same thing with methods and any optional return value, but these are much more common._______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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

A duplicate shouldn’t be necessary; functions/initialisers that can throw just need to be called with a flag indicating whether they should capture or ignore errors as appropriate for try vs try? and try!. Any statement that is identified as being specific to a throw is then wrapped in a conditional based on this flag so it can be skipped if errors are ignored.

Okay, so you're passing in what amounts to a new parameter (which is going to take up a register or some stack space that could be used for something else) and adding a new conditional branch at each throw site. In cases where a `try` is nested directly inside a `throws` function, you might need a conditional branch at the return site, too. (Remember, CPUs hate conditional branches.)

These things aren't free. In fact, they may end up costing more than the original optimization did.

That’s more an issue of developer laziness more than anything

I think that your use of the word "laziness" is telling. You are assuming that, if someone doesn't throw detailed errors, they are being lazy. They are not putting in the work to write good code. They should be judged harshly for this. The language should punish them for their laziness by taking away the tools which allow it.

But sometimes when you don't do work, it's not because you're being lazy; it's because that work is *unnecessary*. You could spend time slaving over an enum full of error codes which will just be converted to an optional, or you could just return an optional in the first place and use the time you saved to do something that will actually improve the product in ways your users will value.

In many simple error handling situations, doing any work beyond indicating success/failure is *unnecessary*. It provides no additional value. We should not hinder the productivity of developers in situations like that simply because sometimes they might misuse the feature permitting it.

but at least it’s still communicating that that’s exactly what the error is. If the standard library includes a good set of default errors then that should cover most use-cases; anything that isn’t covered by a standard error meanwhile absolutely should be given a new error type IMO.

But "invalid parameter" only communicates what's happening because it's so vague that almost any error can be called an "invalid parameter". A malformed path, a path to a file that doesn't exist, and a path to a file in the wrong format are all "invalid parameters". I mean, I guess it tells you that the problem isn't in a property or global or something, but that's not exactly a surprise, is it?

Simply throwing "invalid parameter" conveys no *actually useful* information beyond "that wasn't right", which is exactly what `?` does.

As an aside, I hope you realize that adding a human-readable string is *not helpful*. Without a machine-readable representation, the error cannot be expressed in domain-specific terms ("The ID number included an 'o'; did you mean '0'?") or even easily localized.

I’m not sure what you mean by this exactly; if you mean that an error should include extra data that can be extracted from the error instance itself then that depends on the error.

What I'm saying is that *actually* providing errors with enough detail to be useful is hard work. A string typed directly into your source code, addressed to the developer using your API, in one particular language, with the error's details converted to text and interpolated into it, will not usefully convey the problem. You need to express the problem *programmatically*, and that means carefully cataloguing the causes of errors and deciding for each one how much detail the developer needs.

To illustrate, I spent ten or fifteen minutes examining IntegerParsing.swift.gyb so I could understand the failure cases of `Int.init(_:radix)`. To fully model all of the errors which can cause it to return `nil`, and without including any redundant information you could get from the string itself, you would probably need this enum:

  enum IntFromStringError: ErrorType {
    case EmptyString
    case NoDigits
    case NegativeUnsigned
    case TooLarge (at: String.UTF16View.Index)
    case NonDigit (at: String.UTF16View.Index)
    case DigitBeyondRadix (at: String.UTF16View.Index)
  }

(Note that this does *not* include several errors for invalid radixes which are enforced by preconditions. Evidently, a too-small or too-large radix is considered to be a programmer error.)

A couple of those cases could probably be collapsed into others; it could be three or four instead of six. But think about all that has happened here:

• We've exposed an implementation detail: the algorithm uses the UTF-16 representation of the string. We could convert the index, but then we would be doing work—I suspect invoking an O(N) algorithm!—purely to provide error information which many users would not need. Similarly, if we ever needed to change the implementation, we would either need to make an incompatible change to the error enum or waste time on a conversion. And if we don't include an index at all, then we're not providing enough detail to do anything useful with those errors.

• We still have not modeled the possible errors with full fidelity, because `NegativeUnsigned` is only possible on a `UInt`. We really ought to have separate enums for `IntFromStringError` and `UIntFromStringError`.

• We cannot actually promise that these are the only possible errors this can throw. Currently, you can throw any ErrorType from any throwing function. And in the future, resilience will allow later versions of a library to add cases to enums.

• And we have provided the errors with a level of detail that nearly all callers do not care about. 99.9% of callers will use `try?` and throw away all of this information, so why are we going to so much effort to model it?

And again, if your answer is "Don't do all that, just lump everything together into one vague error", then why are we using the throwing mechanism in the first place? Failable initializers convey one vague error just as well and with much less fuss.

If you’re capturing an invalid parameter error then it will be for debugging purposes I think, so the message is for the developer to inform them of which parameter failed (rather than having to have a different error for each parameter) and possibly some info on why, i.e- I’m not talking about text that would be spit out to a user or logged directly (except perhaps as a debug statement), or that you would expect to capture and process extensively. In this case even a simple string should be plenty helpful compared to nothing.

If an error should only occur during debugging, it should be a precondition, not a failable *or* throwing initializer.

By definition, any throwable error should be something you expect to happen in the normal course of running the code out in the wild. That means there needs to be enough detail to either automatically fix the problem or usefully present it to a user, either textually or with some kind of graphical representation (like pointing to the invalid character).

* * *

Ultimately, here is my point: When faced with a requirement that all errors be thrown, you get two choices.

1. **Throw something super-vague.** This is no better than returning `nil`.
2. **Throw something really specific and useful.** This requires engineering effort which might be better spent elsewhere.

Both options waste computer time and programmer time, and they ultimately stem from prejudging how the developer chooses to allocate their time.

···

--
Brent Royal-Gordon
Architechies

For me the factory method could still be implemented as a class method that returns an optional
And in fact that example used of not creating a duplicate instance would fit here.

I limited this to purely to initialisers as there is a lot more that could get wrong and I felt that in some case falliable initlizers were encouraging people to trivialise something that may bite them in the long run like parsing Json for a model it failing and returning nil. They would have to manually insert breakpoints to figure out why and po the Json.

For normal functions I think there are many acceptable cases where returning an optional instead of an error is okay.

In any case if this is *not* the right approach, I think the upcoming and accepted API design guidelines could be updated from the proposal I am about to submit for this. It would be great to have some guidance of how we should model errors. Right now even after reading the doc I am not sure if throwing an error for my example of parsing Json into a model is correct or if I should just return nil and if I return nil is logging it out to the console a good idea ?

It depends where your JSON come from, what your are doing with it, and a lot of other things. Error model should not be design solely base on the task you perform, but the context too.

For instance, failing to parse a JSON loaded from trusted resource should probably result in a abort(), while failing to load it from user provided URL should result in a error dialog explaining the user what went wrong.

···

Le 7 mars 2016 à 08:15, James Campbell <james@supmenow.com> a écrit :

I will add a consideration in my proposal for updating the new API Guidelines with guidedence for error handling. At the very least it will help clear things up.

Sent from Supmenow.com <http://supmenow.com/&gt;

On Sun, Mar 6, 2016 at 7:27 AM -0800, "Myles Schultz via swift-evolution" <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Mar 6, 2016, at 4:22 AM, Jean-Daniel Dupas via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Just my though, but if this is about redundant feature, why limiting the discussion to initializer. Why not remove Optional as a return type, as any method that return a nil optional on failure may as well throw.

HUGE Nooooooo, and -1. This is so anti-Swift it hurts Swift's feelings, haha. I find some redundancy very, very useful. Not every problem is best handled with one methodology. Removing does not always mean improving. To remove fail able initializers would be disappointing. Sometimes you just don't need an error. What if the init fails because an instance already exists -- the user is just clicking like mad, haha. Well you don't a screen full of "hey, dude, I'm working on it stop clicking already". You just allow for one click, the initializer does it's thing and the user is happy that system didn't crash from error overload. Perhaps that's a poor example, but it's simple for my simple mind. To remove optional as a return type... Dictionary is broken, so is Set... The language just died. And you killed it. This is not a good idea. It would change a massive amount of code both in the language and in my own projects. Absolutely, no, please.

Myles

Le 5 mars 2016 à 23:33, Haravikk via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

On 5 Mar 2016, at 01:18, Brent Royal-Gordon <brent@architechies.com <mailto:brent@architechies.com>> wrote:

1. Exceptions are used to communicate unrecoverable bugs, like null dereferences, divide-by-zero, etc.

This is really a matter of convention, and also more of a matter of terminology. For example, in Swift, they’re just errors rather than exceptions; i.e- there’s no strict indication of their severity, and like any good “exception” model it’s up to the developer if they want to throw an error onwards, silently hide it, do some recovery etc. If you wish to throw a minor error vs a severe one, then you use different error types and document what they represent.

I’d also argue that a failable initialiser represents no less severe an issue than an error does, as the initialiser has still failed, and therefore created nothing usable, ultimately requiring some form of recovery (testing for nil, supplying a default etc.). It isn’t clear at all to me that these are different things, as both can be recoverable or unrecoverable depending upon which form you use it in (try? vs try! vs try/catch, or Foo() vs Foo()!).

This is emphatically not true. Swift represents unrecoverable bugs with precondition failures, not error throwing. Force unwrapping failures (the Swift equivalent of null dereferences), divide-by-zero, and so on are not failures you can catch in Swift.

I think I’ve miscommunicated my meaning; my point is that with error handling you can choose the severity of your error types in terms of whether a caller should handle them or not (though it’s ultimately up to them regardless). They can choose to make any error unrecoverable with try! or an explicit precondition failure too if they like. My point being that if you consider failable initialisers to be somehow less severe than a thrown error, then that’s not really reinforced in any way, as with a throwable initialiser you’re free to throw error types that are minor, e.g- YouProbablyMadeAMistakeError vs SomethingJustExplodedAndPeopleAreDyingError.

There’s nothing that a failable initialiser represents that is distinct from error handling; before error handling was added it was the only option, but now it’s effectively redundant, with less capabilities. Even just the name failable initialiser is a bit of an oddity now as it’s not a failure in the same sense as a precondition failure, i.e- in Swift a failure usually means unrecoverable, which puts a “failable” initialiser on the same footing as any other error.

On 4 Mar 2016, at 23:07, Félix Cloutier <felixcca@yahoo.ca <mailto:felixcca@yahoo.ca>> wrote:
Saying that the optimizer will *probably* make it okay is a very poor justification for replacing a fast feature with a slow one.

As I pointed out it is possible to optimise error handling to be just as efficient as failable initialisers; if the compiler currently doesn’t do this then it absolutely should irrespective of this proposal. At best this is an argument to delay the proposal until such optimisations can be added, otherwise it’s not really an argument against the proposal itself. In other words, if the performance of try? were identical to a failable initialiser (which it could be) then what really is the difference between the two?

I would like to remind that this proposal amounts to you going into my code, and everyone else's code, and break it.

And other features don't? There are a bunch of potentially code-breaking proposals on this mailing list, but that doesn’t eliminate them from consideration if they are justified. Besides which, Swift (or Xcode) have provided automatic conversion where possible to ease transitions in the past, which wouldn’t be hard to do in this case. For example, the following is the same initialiser in failable and throwable forms:

  init(numericValue:String)? {
    guard isNumeric(numericValue) else { return nil }
    …
  }
  init(numericValue:String) throws {
    guard isNumeric(numericValue) else { throw InvalidParameterError() }
    …
  }

And likewise the following two calls to the respective initialisers:

  let value = Foo(numericValue: number)
  let value = try? Foo(numericValue:number)

It’s a fairly straightforward conversion; make failable initialiser throw instead, replacing return nil with throw of a default error type, add try? at previously failable call sites. Of course a developer could (should) go back and tweak the throwable initialiser to throw a more specific error (e.g- InvalidNumberError or such), but the generic error provides just as much information as returning nil did, but with the added option of catching it to find out exactly which part of the initialiser it was thrown from.

However, so far, I don't see any technical merit to it. It's not faster, it's not shorter, it's not better error handling. And even if you think that you can say that it's not worse, you so far have stopped short of showing that it's better.

The main reason against failable initialisers is that given that the above assignments are identical, this means that it is a redundant feature. Requiring the use of thrown errors is definitely better error handling, as it’s far more flexible, and encourages developers to think about the type of errors should be thrown and/or the contents of these errors (i.e- that InvalidParameterError could easily take a string describing why the parameter was invalid such as “Value cannot contain non-numeric characters”) which gives a lot more information about exactly why something has failed, or it could be replaced with a more specific error type.

Can it be faster?

Possibly, but it can be just as fast, as I pointed out.

Unless you mean faster to use, i.e- how long it takes to type return nil vs throw SomeError(), but in that case it’s arguably better for that to be slower, as it forces more consideration of what type of error to present, rather than a generic “something went wrong but I’m not going to tell you what or where it came from so you’ll have to rely on documentation instead”. Developers may still be lazy and not provide any extra information, but that’s true of most things.

Can it be shorter?

No, but that’s arguably a good thing too; by placing try, try? or try! in front of the initialiser it is much clearer at the call site that an error can occur, and how it is being handled. While the optional type system can catch mistakes with failable initialisers, they’re not that common in my experience, so IMO it makes more sense for it to be explicit when an initialiser can’t be relied upon to return a value. You might argue the same thing with methods and any optional return value, but these are much more common.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

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

Here, here! *knock, knock, knock of knuckles on table*

···

Sent from my iPhone

On Mar 7, 2016, at 8:21 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

A duplicate shouldn’t be necessary; functions/initialisers that can throw just need to be called with a flag indicating whether they should capture or ignore errors as appropriate for try vs try? and try!. Any statement that is identified as being specific to a throw is then wrapped in a conditional based on this flag so it can be skipped if errors are ignored.

Okay, so you're passing in what amounts to a new parameter (which is going to take up a register or some stack space that could be used for something else) and adding a new conditional branch at each throw site. In cases where a `try` is nested directly inside a `throws` function, you might need a conditional branch at the return site, too. (Remember, CPUs hate conditional branches.)

These things aren't free. In fact, they may end up costing more than the original optimization did.

That’s more an issue of developer laziness more than anything

I think that your use of the word "laziness" is telling. You are assuming that, if someone doesn't throw detailed errors, they are being lazy. They are not putting in the work to write good code. They should be judged harshly for this. The language should punish them for their laziness by taking away the tools which allow it.

But sometimes when you don't do work, it's not because you're being lazy; it's because that work is *unnecessary*. You could spend time slaving over an enum full of error codes which will just be converted to an optional, or you could just return an optional in the first place and use the time you saved to do something that will actually improve the product in ways your users will value.

In many simple error handling situations, doing any work beyond indicating success/failure is *unnecessary*. It provides no additional value. We should not hinder the productivity of developers in situations like that simply because sometimes they might misuse the feature permitting it.

but at least it’s still communicating that that’s exactly what the error is. If the standard library includes a good set of default errors then that should cover most use-cases; anything that isn’t covered by a standard error meanwhile absolutely should be given a new error type IMO.

But "invalid parameter" only communicates what's happening because it's so vague that almost any error can be called an "invalid parameter". A malformed path, a path to a file that doesn't exist, and a path to a file in the wrong format are all "invalid parameters". I mean, I guess it tells you that the problem isn't in a property or global or something, but that's not exactly a surprise, is it?

Simply throwing "invalid parameter" conveys no *actually useful* information beyond "that wasn't right", which is exactly what `?` does.

As an aside, I hope you realize that adding a human-readable string is *not helpful*. Without a machine-readable representation, the error cannot be expressed in domain-specific terms ("The ID number included an 'o'; did you mean '0'?") or even easily localized.

I’m not sure what you mean by this exactly; if you mean that an error should include extra data that can be extracted from the error instance itself then that depends on the error.

What I'm saying is that *actually* providing errors with enough detail to be useful is hard work. A string typed directly into your source code, addressed to the developer using your API, in one particular language, with the error's details converted to text and interpolated into it, will not usefully convey the problem. You need to express the problem *programmatically*, and that means carefully cataloguing the causes of errors and deciding for each one how much detail the developer needs.

To illustrate, I spent ten or fifteen minutes examining IntegerParsing.swift.gyb so I could understand the failure cases of `Int.init(_:radix)`. To fully model all of the errors which can cause it to return `nil`, and without including any redundant information you could get from the string itself, you would probably need this enum:

   enum IntFromStringError: ErrorType {
       case EmptyString
       case NoDigits
       case NegativeUnsigned
       case TooLarge (at: String.UTF16View.Index)
       case NonDigit (at: String.UTF16View.Index)
       case DigitBeyondRadix (at: String.UTF16View.Index)
   }

(Note that this does *not* include several errors for invalid radixes which are enforced by preconditions. Evidently, a too-small or too-large radix is considered to be a programmer error.)

A couple of those cases could probably be collapsed into others; it could be three or four instead of six. But think about all that has happened here:

• We've exposed an implementation detail: the algorithm uses the UTF-16 representation of the string. We could convert the index, but then we would be doing work—I suspect invoking an O(N) algorithm!—purely to provide error information which many users would not need. Similarly, if we ever needed to change the implementation, we would either need to make an incompatible change to the error enum or waste time on a conversion. And if we don't include an index at all, then we're not providing enough detail to do anything useful with those errors.

• We still have not modeled the possible errors with full fidelity, because `NegativeUnsigned` is only possible on a `UInt`. We really ought to have separate enums for `IntFromStringError` and `UIntFromStringError`.

• We cannot actually promise that these are the only possible errors this can throw. Currently, you can throw any ErrorType from any throwing function. And in the future, resilience will allow later versions of a library to add cases to enums.

• And we have provided the errors with a level of detail that nearly all callers do not care about. 99.9% of callers will use `try?` and throw away all of this information, so why are we going to so much effort to model it?

And again, if your answer is "Don't do all that, just lump everything together into one vague error", then why are we using the throwing mechanism in the first place? Failable initializers convey one vague error just as well and with much less fuss.

If you’re capturing an invalid parameter error then it will be for debugging purposes I think, so the message is for the developer to inform them of which parameter failed (rather than having to have a different error for each parameter) and possibly some info on why, i.e- I’m not talking about text that would be spit out to a user or logged directly (except perhaps as a debug statement), or that you would expect to capture and process extensively. In this case even a simple string should be plenty helpful compared to nothing.

If an error should only occur during debugging, it should be a precondition, not a failable *or* throwing initializer.

By definition, any throwable error should be something you expect to happen in the normal course of running the code out in the wild. That means there needs to be enough detail to either automatically fix the problem or usefully present it to a user, either textually or with some kind of graphical representation (like pointing to the invalid character).

* * *

Ultimately, here is my point: When faced with a requirement that all errors be thrown, you get two choices.

1. **Throw something super-vague.** This is no better than returning `nil`.
2. **Throw something really specific and useful.** This requires engineering effort which might be better spent elsewhere.

Both options waste computer time and programmer time, and they ultimately stem from prejudging how the developer chooses to allocate their time.

--
Brent Royal-Gordon
Architechies

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

A duplicate shouldn’t be necessary; functions/initialisers that can throw just need to be called with a flag indicating whether they should capture or ignore errors as appropriate for try vs try? and try!. Any statement that is identified as being specific to a throw is then wrapped in a conditional based on this flag so it can be skipped if errors are ignored.

Okay, so you're passing in what amounts to a new parameter (which is going to take up a register or some stack space that could be used for something else) and adding a new conditional branch at each throw site. In cases where a `try` is nested directly inside a `throws` function, you might need a conditional branch at the return site, too. (Remember, CPUs hate conditional branches.)

It’s a conditional that should only have an impact when an error is being (or about to be) thrown, also I don’t think that a conditional at the call site would be necessary; if you know that try? or try! was used then you know that the flag was passed and that you’ll get no error instance back, just whatever usually passes the error status.

I think that your use of the word "laziness" is telling. You are assuming that, if someone doesn't throw detailed errors, they are being lazy.

You’re assuming that every error has to be as detailed as possible ;)

I’m working from the assumption that part of updating the standard library to remove failable initialisers would include creating a set of common errors that developers can use too if they like. For example, yes, InvalidParameterError wouldn’t be super informative (though it at least informs you that the issue was with the parameter itself, and not something else that failed internally), however if there were errors for non-numeric input and such that this could provide extra information. You can certainly argue that it could still provide more information, but if we’re considering all failable initialisers to be “simple” then there will be a point at which you’re providing more information than anyone is actually going to use; a developer can certainly choose to do-so, but ultimately there may still be cases where just knowing that there was an error, or an error of a particular type, is enough.

To illustrate, I spent ten or fifteen minutes examining IntegerParsing.swift.gyb so I could understand the failure cases of `Int.init(_:radix)`. To fully model all of the errors which can cause it to return `nil`, and without including any redundant information you could get from the string itself, you would probably need this enum:

  enum IntFromStringError: ErrorType {
    case EmptyString
    case NoDigits
    case NegativeUnsigned
    case TooLarge (at: String.UTF16View.Index)
    case NonDigit (at: String.UTF16View.Index)
    case DigitBeyondRadix (at: String.UTF16View.Index)
  }

if your answer is "Don't do all that, just lump everything together into one vague error", then why are we using the throwing mechanism in the first place? Failable initializers convey one vague error just as well and with much less fuss.

My answer would be more along the lines of “just lump everything together into reasonably specific errors”. For example, EmptyString and NoDigits could easily be handled by a general purpose non-numeric error, while the rest could be handled by an integer out of range error of some kind; that should be plenty to communicate the problem, while detail messages could provide further information to the developer if the error is coming up unexpectedly (e.g- from input you thought was safe).

Instead of “something was wrong” you would then have two possibilities for what was wrong, and the potential to get more information if you need it during testing.

If an error should only occur during debugging, it should be a precondition, not a failable *or* throwing initializer.

By definition, any throwable error should be something you expect to happen in the normal course of running the code out in the wild. That means there needs to be enough detail to either automatically fix the problem or usefully present it to a user, either textually or with some kind of graphical representation (like pointing to the invalid character).

Not quite what I meant; I would absolutely expect the error to occur in the wild, and be caught or ignored as appropriate, the message is just for cases where you’re not sure why it’s occurred in the first place, i.e- the error (and its type) is often enough all you need in simpler error cases, but if you’re getting them unexpectedly rather than due to anticipated mistakes in input etc. then you may want the extra detail. You certainly could model it for programmatic inspection and that’s an option too, but it may not be necessary.

Just because someone opts to use error handling over a failable initialiser doesn’t mean they have to go overboard on the detail of their errors; it’s entirely possible to pick a reasonable middle-ground. In other words, the work you put in should absolutely reflect some kind of value, but you seem to be assuming that the maximum possible amount of work has to be put into every error type thrown, where I think that most simple errors can be adequately modelled from a decent set of default error types; anything more complex absolutely should go into more detail as appropriate, but again they don’t necessarily have to expose the most minute of details.

···

On 8 Mar 2016, at 01:21, Brent Royal-Gordon <brent@architechies.com> wrote:

Well put!

+1 to everything Brent said!

-Thorsten

···

Am 08.03.2016 um 02:21 schrieb Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org>:

A duplicate shouldn’t be necessary; functions/initialisers that can throw just need to be called with a flag indicating whether they should capture or ignore errors as appropriate for try vs try? and try!. Any statement that is identified as being specific to a throw is then wrapped in a conditional based on this flag so it can be skipped if errors are ignored.

Okay, so you're passing in what amounts to a new parameter (which is going to take up a register or some stack space that could be used for something else) and adding a new conditional branch at each throw site. In cases where a `try` is nested directly inside a `throws` function, you might need a conditional branch at the return site, too. (Remember, CPUs hate conditional branches.)

These things aren't free. In fact, they may end up costing more than the original optimization did.

That’s more an issue of developer laziness more than anything

I think that your use of the word "laziness" is telling. You are assuming that, if someone doesn't throw detailed errors, they are being lazy. They are not putting in the work to write good code. They should be judged harshly for this. The language should punish them for their laziness by taking away the tools which allow it.

But sometimes when you don't do work, it's not because you're being lazy; it's because that work is *unnecessary*. You could spend time slaving over an enum full of error codes which will just be converted to an optional, or you could just return an optional in the first place and use the time you saved to do something that will actually improve the product in ways your users will value.

In many simple error handling situations, doing any work beyond indicating success/failure is *unnecessary*. It provides no additional value. We should not hinder the productivity of developers in situations like that simply because sometimes they might misuse the feature permitting it.

but at least it’s still communicating that that’s exactly what the error is. If the standard library includes a good set of default errors then that should cover most use-cases; anything that isn’t covered by a standard error meanwhile absolutely should be given a new error type IMO.

But "invalid parameter" only communicates what's happening because it's so vague that almost any error can be called an "invalid parameter". A malformed path, a path to a file that doesn't exist, and a path to a file in the wrong format are all "invalid parameters". I mean, I guess it tells you that the problem isn't in a property or global or something, but that's not exactly a surprise, is it?

Simply throwing "invalid parameter" conveys no *actually useful* information beyond "that wasn't right", which is exactly what `?` does.

As an aside, I hope you realize that adding a human-readable string is *not helpful*. Without a machine-readable representation, the error cannot be expressed in domain-specific terms ("The ID number included an 'o'; did you mean '0'?") or even easily localized.

I’m not sure what you mean by this exactly; if you mean that an error should include extra data that can be extracted from the error instance itself then that depends on the error.

What I'm saying is that *actually* providing errors with enough detail to be useful is hard work. A string typed directly into your source code, addressed to the developer using your API, in one particular language, with the error's details converted to text and interpolated into it, will not usefully convey the problem. You need to express the problem *programmatically*, and that means carefully cataloguing the causes of errors and deciding for each one how much detail the developer needs.

To illustrate, I spent ten or fifteen minutes examining IntegerParsing.swift.gyb so I could understand the failure cases of `Int.init(_:radix)`. To fully model all of the errors which can cause it to return `nil`, and without including any redundant information you could get from the string itself, you would probably need this enum:

   enum IntFromStringError: ErrorType {
       case EmptyString
       case NoDigits
       case NegativeUnsigned
       case TooLarge (at: String.UTF16View.Index)
       case NonDigit (at: String.UTF16View.Index)
       case DigitBeyondRadix (at: String.UTF16View.Index)
   }

(Note that this does *not* include several errors for invalid radixes which are enforced by preconditions. Evidently, a too-small or too-large radix is considered to be a programmer error.)

A couple of those cases could probably be collapsed into others; it could be three or four instead of six. But think about all that has happened here:

• We've exposed an implementation detail: the algorithm uses the UTF-16 representation of the string. We could convert the index, but then we would be doing work—I suspect invoking an O(N) algorithm!—purely to provide error information which many users would not need. Similarly, if we ever needed to change the implementation, we would either need to make an incompatible change to the error enum or waste time on a conversion. And if we don't include an index at all, then we're not providing enough detail to do anything useful with those errors.

• We still have not modeled the possible errors with full fidelity, because `NegativeUnsigned` is only possible on a `UInt`. We really ought to have separate enums for `IntFromStringError` and `UIntFromStringError`.

• We cannot actually promise that these are the only possible errors this can throw. Currently, you can throw any ErrorType from any throwing function. And in the future, resilience will allow later versions of a library to add cases to enums.

• And we have provided the errors with a level of detail that nearly all callers do not care about. 99.9% of callers will use `try?` and throw away all of this information, so why are we going to so much effort to model it?

And again, if your answer is "Don't do all that, just lump everything together into one vague error", then why are we using the throwing mechanism in the first place? Failable initializers convey one vague error just as well and with much less fuss.

If you’re capturing an invalid parameter error then it will be for debugging purposes I think, so the message is for the developer to inform them of which parameter failed (rather than having to have a different error for each parameter) and possibly some info on why, i.e- I’m not talking about text that would be spit out to a user or logged directly (except perhaps as a debug statement), or that you would expect to capture and process extensively. In this case even a simple string should be plenty helpful compared to nothing.

If an error should only occur during debugging, it should be a precondition, not a failable *or* throwing initializer.

By definition, any throwable error should be something you expect to happen in the normal course of running the code out in the wild. That means there needs to be enough detail to either automatically fix the problem or usefully present it to a user, either textually or with some kind of graphical representation (like pointing to the invalid character).

* * *

Ultimately, here is my point: When faced with a requirement that all errors be thrown, you get two choices.

1. **Throw something super-vague.** This is no better than returning `nil`.
2. **Throw something really specific and useful.** This requires engineering effort which might be better spent elsewhere.

Both options waste computer time and programmer time, and they ultimately stem from prejudging how the developer chooses to allocate their time.

--
Brent Royal-Gordon
Architechies

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

Interestingly what Haravikk describes is mentioned here.

This approach is very similar to Rubys

···

*___________________________________*

*James⎥Head of Trolls*

*james@supmenow.com <james@supmenow.com>⎥supmenow.com <http://supmenow.com>*

*Sup*

*Runway East *

*10 Finsbury Square*

*London*

* EC2A 1AF *

On Tue, Mar 8, 2016 at 10:07 AM, Haravikk via swift-evolution < swift-evolution@swift.org> wrote:

> On 8 Mar 2016, at 01:21, Brent Royal-Gordon <brent@architechies.com> > wrote:
>
>> A duplicate shouldn’t be necessary; functions/initialisers that can
throw just need to be called with a flag indicating whether they should
capture or ignore errors as appropriate for try vs try? and try!. Any
statement that is identified as being specific to a throw is then wrapped
in a conditional based on this flag so it can be skipped if errors are
ignored.
>
> Okay, so you're passing in what amounts to a new parameter (which is
going to take up a register or some stack space that could be used for
something else) and adding a new conditional branch at each throw site. In
cases where a `try` is nested directly inside a `throws` function, you
might need a conditional branch at the return site, too. (Remember, CPUs
hate conditional branches.)

It’s a conditional that should only have an impact when an error is being
(or about to be) thrown, also I don’t think that a conditional at the call
site would be necessary; if you know that try? or try! was used then you
know that the flag was passed and that you’ll get no error instance back,
just whatever usually passes the error status.

> I think that your use of the word "laziness" is telling. You are
assuming that, if someone doesn't throw detailed errors, they are being
lazy.

You’re assuming that every error has to be as detailed as possible ;)

I’m working from the assumption that part of updating the standard library
to remove failable initialisers would include creating a set of common
errors that developers can use too if they like. For example, yes,
InvalidParameterError wouldn’t be super informative (though it at least
informs you that the issue was with the parameter itself, and not something
else that failed internally), however if there were errors for non-numeric
input and such that this could provide extra information. You can certainly
argue that it could still provide more information, but if we’re
considering all failable initialisers to be “simple” then there will be a
point at which you’re providing more information than anyone is actually
going to use; a developer can certainly choose to do-so, but ultimately
there may still be cases where just knowing that there was an error, or an
error of a particular type, is enough.

> To illustrate, I spent ten or fifteen minutes examining
IntegerParsing.swift.gyb so I could understand the failure cases of
`Int.init(_:radix)`. To fully model all of the errors which can cause it to
return `nil`, and without including any redundant information you could get
from the string itself, you would probably need this enum:
>
> enum IntFromStringError: ErrorType {
> case EmptyString
> case NoDigits
> case NegativeUnsigned
> case TooLarge (at: String.UTF16View.Index)
> case NonDigit (at: String.UTF16View.Index)
> case DigitBeyondRadix (at: String.UTF16View.Index)
> }
>
> if your answer is "Don't do all that, just lump everything together into
one vague error", then why are we using the throwing mechanism in the first
place? Failable initializers convey one vague error just as well and with
much less fuss.

My answer would be more along the lines of “just lump everything together
into reasonably specific errors”. For example, EmptyString and NoDigits
could easily be handled by a general purpose non-numeric error, while the
rest could be handled by an integer out of range error of some kind; that
should be plenty to communicate the problem, while detail messages could
provide further information to the developer if the error is coming up
unexpectedly (e.g- from input you thought was safe).

Instead of “something was wrong” you would then have two possibilities for
what was wrong, and the potential to get more information if you need it
during testing.

> If an error should only occur during debugging, it should be a
precondition, not a failable *or* throwing initializer.
>
> By definition, any throwable error should be something you expect to
happen in the normal course of running the code out in the wild. That means
there needs to be enough detail to either automatically fix the problem or
usefully present it to a user, either textually or with some kind of
graphical representation (like pointing to the invalid character).

Not quite what I meant; I would absolutely expect the error to occur in
the wild, and be caught or ignored as appropriate, the message is just for
cases where you’re not sure why it’s occurred in the first place, i.e- the
error (and its type) is often enough all you need in simpler error cases,
but if you’re getting them unexpectedly rather than due to anticipated
mistakes in input etc. then you may want the extra detail. You certainly
could model it for programmatic inspection and that’s an option too, but it
may not be necessary.

Just because someone opts to use error handling over a failable
initialiser doesn’t mean they have to go overboard on the detail of their
errors; it’s entirely possible to pick a reasonable middle-ground. In other
words, the work you put in should absolutely reflect some kind of value,
but you seem to be assuming that the maximum possible amount of work has to
be put into every error type thrown, where I think that most simple errors
can be adequately modelled from a decent set of default error types;
anything more complex absolutely should go into more detail as appropriate,
but again they don’t necessarily have to expose the most minute of details.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

And it is entitely possible to just use an optional. If you prefer to use error handling over failable initializers, fine, just do so, but don't force it on others who are perfectly happy with the option to use optionals, too.

-Thorsten

···

Am 08.03.2016 um 11:07 schrieb Haravikk via swift-evolution <swift-evolution@swift.org>:

Just because someone opts to use error handling over a failable initialiser doesn’t mean they have to go overboard on the detail of their errors; it’s entirely possible to pick a reasonable middle-ground.

Failable initializers are great when you don't care what kind of error
happened and can handle it locally and the same way for any error (for
example by skipping an element).

I'd be very sad to lose them.

···

On Tue, Mar 8, 2016 at 10:26 AM Thorsten Seitz via swift-evolution < swift-evolution@swift.org> wrote:

> Am 08.03.2016 um 11:07 schrieb Haravikk via swift-evolution < > swift-evolution@swift.org>:
>
> Just because someone opts to use error handling over a failable
initialiser doesn’t mean they have to go overboard on the detail of their
errors; it’s entirely possible to pick a reasonable middle-ground.

And it is entitely possible to just use an optional. If you prefer to use
error handling over failable initializers, fine, just do so, but don't
force it on others who are perfectly happy with the option to use
optionals, too.

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

It’s not an issue of ideology but of redundancy; the failable initialiser does nothing that error handling can’t do just as easily, the only difference is that instead of returning nil, you throw an appropriate error.

The few extra characters are hardly going to kill you, while a thrown error (with common ones available for simplicity) can describe what went wrong in more detail than just “something went wrong”. Point is that we have two ways of achieving the same goal, but error handling encourages developers to think more about what type(s) of error to throw at each point; even just simple error types with no further detail can provide more information simply by being different, for example if you have a NonNumericError vs EmptyStringError types, the errors themselves tell you all you’re likely to need to know (hopefully there’d be a good set of common types).

There are also some cases where failable initialisers can have subtle errors, for example, can you tell me where I might run into problems with the following:

struct MyType {
    let elements:[String]

    func asInt(index:Array<String>.Index) -> Int? {
        if self.elements.indices.contains(index) {
            return Int(self.elements[index])
        }
        return nil
    }
}

···

On 8 Mar 2016, at 15:26, Thorsten Seitz <tseitz42@icloud.com> wrote:

Am 08.03.2016 um 11:07 schrieb Haravikk via swift-evolution <swift-evolution@swift.org>:

Just because someone opts to use error handling over a failable initialiser doesn’t mean they have to go overboard on the detail of their errors; it’s entirely possible to pick a reasonable middle-ground.

And it is entitely possible to just use an optional. If you prefer to use error handling over failable initializers, fine, just do so, but don't force it on others who are perfectly happy with the option to use optionals, too.

If the argument is that taking away "init?()" is going to force Swift users
to embrace the One True Path of Error Handling, I don't think that's going
to happen. The more likely outcome is that people are going to wrap
throwable initializers in factory methods returning optionals, and throw
away whatever error returns.

Not that this is a bad thing, mind you. In a lot of cases my application
does not care why an operation failed, only that it did. This isn't because
I'm lazy, but because the recovery path for each potential type of error is
exactly the same. In this case I don't care about which error was thrown,
but I often do care about the ability to compose I get from nil.

As for the sample code, there is absolutely nothing wrong with it IMO. The
contract to me for 'asInt' reads "return an Int if an item exists at
`index` and, if that item exists, it can be represented as an Int". I think
it's instructional to look at how Dictionary<T?> works - if you try to
extract an element you get a double optional. This isn't a type system bug,
it's completely intentional. The internal optional indicates whether an
extant element was Some or None, and the external optional indicates
whether or not the element existed in the dictionary to begin with. If I
were writing that `asInt` function and I really needed to make the
distinction between "invalid index" and "not an integer string", I'd use
either that same double optional pattern, or make the function throws. If I
didn't care, I'd use the optional representation.

Best,
Austin

···

On Tue, Mar 8, 2016 at 1:09 PM, Haravikk via swift-evolution < swift-evolution@swift.org> wrote:

On 8 Mar 2016, at 15:26, Thorsten Seitz <tseitz42@icloud.com> wrote:

Am 08.03.2016 um 11:07 schrieb Haravikk via swift-evolution < > swift-evolution@swift.org>:

Just because someone opts to use error handling over a failable
initialiser doesn’t mean they have to go overboard on the detail of their
errors; it’s entirely possible to pick a reasonable middle-ground.

And it is entitely possible to just use an optional. If you prefer to use
error handling over failable initializers, fine, just do so, but don't
force it on others who are perfectly happy with the option to use
optionals, too.

It’s not an issue of ideology but of redundancy; the failable initialiser
does nothing that error handling can’t do just as easily, the only
difference is that instead of returning nil, you throw an appropriate error.

The few extra characters are hardly going to kill you, while a thrown
error (with common ones available for simplicity) can describe what went
wrong in more detail than just “something went wrong”. Point is that we
have two ways of achieving the same goal, but error handling encourages
developers to think more about what type(s) of error to throw at each
point; even just simple error types with no further detail can provide more
information simply by being different, for example if you have a
NonNumericError vs EmptyStringError types, the errors themselves tell you
all you’re likely to need to know (hopefully there’d be a good set of
common types).

There are also some cases where failable initialisers can have subtle
errors, for example, can you tell me where I might run into problems with
the following:

struct MyType {
    let elements:[String]

    func asInt(index:Array<String>.Index) -> Int? {
        if self.elements.indices.contains(index) {
            return Int(self.elements[index])
        }
        return nil
    }
}

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