[swift-evolution] [Pre-Draft] Nil-coalescing and errors

Haravikk swift-evolution at haravikk.me
Wed Apr 6 11:23:12 CDT 2016


I’m inclined to agree with this; the guard statement is fairly clear, though a slightly cleaner construct for throwing would be nice.
That said, it isn’t mutually exclusive with the MikeAsh alternative provided, which could still be a nice addition for those that prefer it (though it’s also a fairly easy one to add yourself through extension).

Coincidentally, the example in the operator part of the proposal illustrates part of why I argued against keeping failable initialisers as a type of error handling as it can be easy to result in odd cases when nil is used alternatively as an error condition and simply a “no result” case. But that’s another topic really.

> On 6 Apr 2016, at 16:00, Sean Heber via swift-evolution <swift-evolution at swift.org> wrote:
> 
> Interesting, but I’m unsure if all of it is significantly better than just using the guard that is effectively inside of the operator/func that is being proposed:
> 
> guard let value = Int("NotANumber") else { throw InitializerError.invalidString }
> 
> It is only a couple of characters longer and already works (it’s what I use currently). If guard allowed for a special single-expression variation so that you didn’t need to specify the ugly braces or something, it’d look prettier and be nice for a lot of other situations, too:
> 
> guard let value = Int("NotANumber") else: throw InitializerError.invalidString
> guard someVal < 10 else: return false
> guard mustBeTrue() else: return
> // etc
> 
> Not to derail this, but I sort of want this ability anywhere as a shorthand for a single-expression block.
> 
> if something < 42: doThing()
> for a in list: print(a)
> 
> But I imagine that’ll never fly. :P
> 
> l8r
> Sean
> 
> 
> 
>> On Apr 6, 2016, at 9:46 AM, Erica Sadun via swift-evolution <swift-evolution at swift.org> wrote:
>> 
>> Pyry Jahkola and I have been plugging away on the following which is preliminary enough not to qualify as an actual draft. He prefers the Mike Ash approach. I prefer the operator approach. So we have not actually settled on which one we would actually propose despite how I've written this up.
>> 
>> I'm putting this out there to try to gain a consensus on:
>> 
>> * Would this be a viable proposal?
>> * If so, which of the options would work best within Swift's design and philosophy 
>> 
>> Thanks for your feedback.
>> 
>> -- Erica
>> Introduction
>> 
>> Swift's try? keyword transforms error-throwing operations into optional values. We propose adding an error-throwing nil-coalescing operator to the Swift standard library. This operator will coerce optional results into Swift's error-handling system. 
>> 
>> This proposal was discussed on the Swift Evolution list in the name thread.
>> 
>> Motivation
>> 
>> Any decision to expand Swift's set of standard operators should be taken thoughtfully and judiciously. Moving unaudited or deliberately non-error-handling nil-returning methods and failable initializers into Swift's error system should be a common enough use case to justify introducing a new operator.
>> 
>> Detail Design
>> 
>> We propose adding a new operator that works along the following lines:
>> 
>> infix operator ??? {}
>> 
>> func ???<T>(lhs: T?, @autoclosure error: () -> ErrorType) throws -> T {
>>    guard case let value? = lhs else { throw error() }
>>    return value
>> }
>> 
>> The use-case would look like this:
>> 
>> do {
>>    let error = Error(reason: "Invalid string passed to Integer initializer")
>>    let value = try Int("NotANumber") ??? InitializerError.invalidString
>>    print("Value", value)
>> } catch { print(error) }
>> 
>> Note
>> 
>> SE-0047 (warn unused result by default) and SE-0049 (move autoclosure) both affect many of the snippets in this proposal
>> 
>> Disadvantages to this approach:
>> 
>> 	• It consumes a new operator, which developers must be trained to use
>> 	• Unlike many other operators and specifically ??, this cannot be chained. There's no equivalent to a ?? b ?? c ?? dor a ?? (b ?? (c ?? d)).
>> Alternatives Considered
>> 
>> Extending Optional
>> 
>> The MikeAsh approach extends Optional to add an orThrow(ErrorType) method
>> 
>> extension Optional {
>>    func orThrow(@autoclosure error: () -> ErrorType) throws -> Wrapped {
>>        guard case let value? = self else { throw error() }
>>        return value
>>    }
>> }
>> 
>> Usage looks like this:
>> 
>> do {
>>    let value = try Int("NotANumber")
>>        .orThrow(InitializerError.invalidString)
>>    print("Value", value)
>> } catch { print(error) }
>> 
>> An alternative version of this call looks like this: optionalValue.or(throw: error). I am not a fan of using a verb as a first statement label.
>> 
>> Disadvantages:
>> 
>> 	• Wordier than the operator, verging on claustrophobic, even using Swift's newline dot continuation.
>> 	• Reading the code can be confusing. This requires chaining rather than separating error throwing into a clear separate component. 
>> Advantages:
>> 
>> 	• No new operator, which maintains Swift operator parsimony and avoids the introduction and training issues associated with new operators.
>> 	• Implicit Optional promotion cannot take place. You avoid mistaken usage like nonOptional ??? error and nonOptional ?? raise(error).
>> 	• As a StdLib method, autocompletion support is baked in.
>> Introducing a StdLib implementation of raise(ErrorType)
>> 
>> Swift could introduce a raise(ErrorType) -> T global function:
>> 
>> func raise<T>(error: ErrorType) throws -> T { throw error }
>> 
>> do {
>>    let value = try Int("NotANumber") ?? raise(InitializerError.invalidString)
>>    print("Value", value)
>> } catch { print(error) }
>> 
>> This is less than ideal:
>> 
>> 	• This approach is similar to using && as an if-true condition where an operator is abused for its side-effects.
>> 	• It is wordier than the operator approach.
>> 	• The error raising function promises to return a type but never will, which seems hackish.
>> Overriding ??
>> 
>> We also considered overriding ?? to accept an error as a RHS argument. This introduces a new way to interpret ?? as meaning, "throw this error instead of substituting this value".
>> 
>> func ??<T>(lhs: T?, @autoclosure error: () -> ErrorType) throws -> T {
>>    guard case let value? = lhs else { throw error() }
>>    return value
>> }
>> 
>> Usage:
>> 
>> let value = try Int("NotANumber") ?? Error(reason: "Invalid string passed to Integer initializer")
>> 
>> This approach overloads the semantics as well as the syntax of the coalescing operator. Instead of falling back to a RHS value, it raises the RHS error. The code remains simple and readable although the developer must take care to clarify through comments and naming which version of the operator is being used.
>> 
>> 	• While using try in the ?? statement signals that a throwing call is in use, it is insufficient (especially when used in a throwing scope) to distinguish between the normal coalescing and new error-throwing behaviors.
>> 	• Error types need not use the word "Error" in their construction or use. For example try value ?? e may not be immediately clear as an error-throwing intent.
>> 	• Overloading ?? dilutes the impact and meaning of the original operator intent.
>> Future Directions
>> 
>> We briefly considered something along the lines of perl's die as an alternative to raise using fatalError.
>> 
>> Acknowledgements
>> 
>> Thanks Mike Ash, Jido, Dave Delong
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution



More information about the swift-evolution mailing list