[swift-evolution] Remove Failable Initializers

Jean-Daniel Dupas mailing at xenonium.com
Mon Mar 7 01:44:46 CST 2016


> Le 7 mars 2016 à 08:15, James Campbell <james at supmenow.com> a écrit :
> 
> 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.

> 
> 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/>
> 
> 
> 
> On Sun, Mar 6, 2016 at 7:27 AM -0800, "Myles Schultz via swift-evolution" <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
> 
> 
> 
> On Mar 6, 2016, at 4:22 AM, Jean-Daniel Dupas via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at 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 at swift.org <mailto:swift-evolution at swift.org>> a écrit :
>>> 
>>> 
>>>> On 5 Mar 2016, at 01:18, Brent Royal-Gordon <brent at architechies.com <mailto:brent at 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 at yahoo.ca <mailto:felixcca at 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 at swift.org <mailto:swift-evolution at swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160307/3b653b8b/attachment.html>


More information about the swift-evolution mailing list