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

Yuta Koshizawa koher at koherent.org
Wed Apr 6 20:04:10 CDT 2016


I agree with this and I like the operator approach.

Besides the proposed infix `???`, I also want the postfix one which
throws a `NilError` (something like `struct NilError: ErrorType {}`).
It is useful to handle multiple `nil`s at once when we are not
interested in the kind of the error.

```
// Decodes a JSON with SwiftyJSON
do {
  let person: Person = try Person(
    firstName: json["firstName"].string???,
    lastName: json["lastName"].string???,
    age: json["age"].int???
  )
} catch _ {
  // Error handling
}
```

Considering the postfix one, the operator approach for the infix one
is consistent.

One more thing; I think `???` is too long. Instead, I propose `|?`.
For `foo: Foo?`, `try foo|?` can be read like `Foo` or `nil`. It
separates (`|`) nil (`?`) from the value and return `Foo`. I think it
makes sense.

-- Yuta

2016-04-06 23:46 GMT+09:00 Erica Sadun via swift-evolution
<swift-evolution at swift.org>:
> 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
>


More information about the swift-evolution mailing list