[Pre-Draft] Nil-coalescing and errors


(Erica Sadun) #1

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
<https://gist.github.com/erica/5a26d523f3d6ffb74e34d179740596f7#introduction>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 <https://gist.github.com/erica/link> thread.

<https://gist.github.com/erica/5a26d523f3d6ffb74e34d179740596f7#motivation>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.

<https://gist.github.com/erica/5a26d523f3d6ffb74e34d179740596f7#detail-design>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) }
<https://gist.github.com/erica/5a26d523f3d6ffb74e34d179740596f7#note>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)).
<https://gist.github.com/erica/5a26d523f3d6ffb74e34d179740596f7#alternatives-considered>Alternatives Considered

<https://gist.github.com/erica/5a26d523f3d6ffb74e34d179740596f7#extending-optional>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.
<https://gist.github.com/erica/5a26d523f3d6ffb74e34d179740596f7#introducing-a-stdlib-implementation-of-raiseerrortype>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.
<https://gist.github.com/erica/5a26d523f3d6ffb74e34d179740596f7#overriding->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.
<https://gist.github.com/erica/5a26d523f3d6ffb74e34d179740596f7#future-directions>Future Directions

We briefly considered something along the lines of perl's die as an alternative to raise using fatalError.

<https://gist.github.com/erica/5a26d523f3d6ffb74e34d179740596f7#acknowledgements>Acknowledgements

Thanks Mike Ash, Jido, Dave Delong


(Sean Heber) #2

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. :stuck_out_tongue:

l8r
Sean

···

On Apr 6, 2016, at 9:46 AM, Erica Sadun via swift-evolution <swift-evolution@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Yuta Koshizawa) #3

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@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Guillaume Lessard) #4

A different direction would be to add a non-autoclosure variant to ?? that explicitly takes a closure.

public func ??<T>(optional: T?, defaultValue: () throws -> T) rethrows -> T {
  switch optional {
  case .Some(let wrapped): return wrapped
  case .None: return try defaultValue()
  }
}

Then, the following works:

var v = Optional(7)
// v = nil
do {
  let i = try v ?? { throw NSError(domain: "", code: 0, userInfo: [:]) }
  print(i)
}
catch {
  print(error)
}

Or, even more generally, allow functions and operators that take autoclosure parameters to be used with explicit closures? I shrugged my shoulders and wrote an overload when I hit that limitation, but I wonder whether it is necessary.

Cheers,
Guillaume Lessard


(Erica Sadun) #5

That is a pretty damn compelling argument.

-- E

···

On Apr 6, 2016, at 9:00 AM, Sean Heber <sean@fifthace.com> 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 }


(Brent Royal-Gordon) #6

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 }

That is a pretty damn compelling argument.

For some cases, yes. For others…

  myInt = Int("NotANumber") ?? throw InitializerError.invalidString

On the other hand, all we really need is a generalized "noneMap" function marked as rethrowing, which can serve multiple purposes.

  myOtherInt = Int("NotANumber").noneMap(arc4random)
  myInt = try Int("NotANumber").noneMap { throw InitializerError.invalidString }

On the gripping hand: I think this is only a problem because `throw` is a statement, not an expression. Could it be changed to be an expression with an unspecified return type? I believe that would allow you to simply drop it into the right side of a ?? operator, and anywhere else you might want it (boolean operators, for instance).

···

--
Brent Royal-Gordon
Architechies


(Jordan Rose) #7

I think I'm with Sean on this one. Optionals and throwing don't have enough to do with each other to actually come up with a specific operator or method for this. I can't help but see this as two ideas glued together:

- "By this point in my execution I need a non-optional value, otherwise ______"
- "_____ happened, therefore execution has failed and I should throw an error"

…and I'm not sure these ideas coincide enough to be worth gluing together. There are a lot of other ways to get a non-optional value out of an optional ('??', '!', and 'guard let' with some other action), and there are a lot of other ways to fail besides an optional being nil (status code came back as error, unexpected data, connection timeout).

I'd like to see some real-world examples of this before we did anything with it.

Jordan

···

On Apr 6, 2016, at 8:00, Sean Heber via swift-evolution <swift-evolution@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. :stuck_out_tongue:

l8r
Sean

On Apr 6, 2016, at 9:46 AM, Erica Sadun via swift-evolution <swift-evolution@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@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


(Haravikk) #8

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@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. :stuck_out_tongue:

l8r
Sean

On Apr 6, 2016, at 9:46 AM, Erica Sadun via swift-evolution <swift-evolution@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@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


(Pyry Jahkola) #9

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 }

That is a pretty damn compelling argument.

Made me smile too. :slight_smile:

For some cases, yes. For others…

Right. Continuing that thought, Swift is not a language where there's exactly one way of doing everything. For `Optional` unwrapping, we have `if let`, `guard let`, `x.map(f)`, `x.flatMap(f)`, `x?.member`, `x ?? y`, and a bunch of others. The fact that you can already do almost the same thing with a statement doesn't rule out the occasional need to do the same thing in just an expression.

But like it was said, it's pretty easy to add this in a user-defined extension too, so it remains a question of whether people in general see `x ??? error` or `x.orThrow(error)` as a useful thing to have.

  myInt = Int("NotANumber") ?? throw InitializerError.invalidString

The following works already:

  myInt = Int("NotANumber") ?? { throw InitializerError.invalidString }()

(Ping Erica, to the list of alternatives considered.)

On the other hand, all we really need is a generalized "noneMap" function marked as rethrowing, which can serve multiple purposes.

  myOtherInt = Int("NotANumber").noneMap(arc4random)
  myInt = try Int("NotANumber").noneMap { throw InitializerError.invalidString }

If I got it right, you're after this extension:

  extension Optional {
      func noneMap(ifNone: () throws -> Wrapped) rethrows -> Wrapped {
          return try self ?? ifNone()
      }
  }

Since we have `??` already, I feel `noneMap` isn't different enough to justify being added to the standard library.

On the gripping hand: I think this is only a problem because `throw` is a statement, not an expression. Could it be changed to be an expression with an unspecified return type? I believe that would allow you to simply drop it into the right side of a ?? operator, and anywhere else you might want it (boolean operators, for instance).

Why not. That's essentially making the `x ?? raise(error)` trick into a language feature. My guts say people wouldn't like how it looks. Would've suggested that as another option otherwise.

All in all, I think the error handling model introduced in Swift 2.0 was pretty heavy on syntax compared to how much new functionality it made possible (and even now, we don't have catch exhaustiveness checking or asynchronous error handling, to name a few). I'd rather improve Swift's error handling by introducing features on the library side and only inventing new syntax for things that can't be done in code.

— Pyry


(John McCall) #10

For what it's worth, this is how it was originally designed. Reducing it to a statement was a simplification to gain consensus.

John.

···

On Apr 6, 2016, at 9:21 AM, Brent Royal-Gordon via swift-evolution <swift-evolution@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 }

That is a pretty damn compelling argument.

For some cases, yes. For others…

  myInt = Int("NotANumber") ?? throw InitializerError.invalidString

On the other hand, all we really need is a generalized "noneMap" function marked as rethrowing, which can serve multiple purposes.

  myOtherInt = Int("NotANumber").noneMap(arc4random)
  myInt = try Int("NotANumber").noneMap { throw InitializerError.invalidString }

On the gripping hand: I think this is only a problem because `throw` is a statement, not an expression. Could it be changed to be an expression with an unspecified return type? I believe that would allow you to simply drop it into the right side of a ?? operator, and anywhere else you might want it (boolean operators, for instance).


(David Hart) #11

I agree with Sean, I ready use guard and don't see much use in a StdLib or operator alternative.

···

Sent from my iPad

On 06 Apr 2016, at 17:00, Sean Heber via swift-evolution <swift-evolution@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. :stuck_out_tongue:

l8r
Sean

On Apr 6, 2016, at 9:46 AM, Erica Sadun via swift-evolution <swift-evolution@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@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


(Howard Lovatt) #12

Personally I would like Optional to gain `ifNil` (`??` would call
`ifNil`) and `ifNilThrow`, both of which have an auto closure argument
returning a value and throwing respectively. This would save adding an
extra operator and allow chaining to read better than `??` in some
circumstances, like when trailing closures are used, e.g:

    let value = array.map { ... }
                             .filter { ... }
                             .firstElement
                             .ifNil { ... }

Reads better than:

    let value = array.map { ... }
                             .filter { ... }
                             .firstElement ?? { ... }

Because the `??` tends to vanish and it looks like firstElement has a
trailing closure.

···

On Thursday, 7 April 2016, Jordan Rose via swift-evolution < swift-evolution@swift.org> wrote:

I think I'm with Sean on this one. Optionals and throwing don't have
enough to do with each other to actually come up with a specific operator
or method for this. I can't help but see this as two ideas glued together:

- "By this point in my execution I need a non-optional value, otherwise
______"
- "_____ happened, therefore execution has failed and I should throw an
error"

…and I'm not sure these ideas coincide enough to be *worth* gluing
together. There are a lot of other ways to get a non-optional value out of
an optional ('??', '!', and 'guard let' with some other action), and there
are a lot of other ways to fail besides an optional being nil (status code
came back as error, unexpected data, connection timeout).

I'd like to see some real-world examples of this before we did anything
with it.

Jordan

On Apr 6, 2016, at 8:00, Sean Heber via swift-evolution < > swift-evolution@swift.org > <javascript:_e(%7B%7D,'cvml','swift-evolution@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. :stuck_out_tongue:

l8r
Sean

On Apr 6, 2016, at 9:46 AM, Erica Sadun via swift-evolution < > swift-evolution@swift.org > <javascript:_e(%7B%7D,'cvml','swift-evolution@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@swift.org
<javascript:_e(%7B%7D,'cvml','swift-evolution@swift.org');>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
<javascript:_e(%7B%7D,'cvml','swift-evolution@swift.org');>
https://lists.swift.org/mailman/listinfo/swift-evolution

--
-- Howard.


(Yuta Koshizawa) #13

I'd like to see some real-world examples of this before we did anything with it.

The following is my real-world example.

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

With `guard`, we have to repeat the parameter names meaninglessly.

do {
    guard let firstName = json["firstName"].string else { throw Error() }
    guard let lastName = json["lastName"].string else { throw Error() }
    guard let age = json["age"].string else { throw Error() }
    let person: Person = Person(firstName: firstName, lastName:
lastName, age: age)
} catch _ {
  // Error handling
}

`guard` is a statement. `???` makes an expression. Expressions are
useful when we want to pass their return values as arguments directly.
I think `???` is valuable to get an unwrapped value or throw an error
as an expression.

-- Yuta

···

2016-04-07 2:45 GMT+09:00 Jordan Rose via swift-evolution <swift-evolution@swift.org>:

I think I'm with Sean on this one. Optionals and throwing don't have enough
to do with each other to actually come up with a specific operator or method
for this. I can't help but see this as two ideas glued together:

- "By this point in my execution I need a non-optional value, otherwise
______"
- "_____ happened, therefore execution has failed and I should throw an
error"

…and I'm not sure these ideas coincide enough to be worth gluing together.
There are a lot of other ways to get a non-optional value out of an optional
('??', '!', and 'guard let' with some other action), and there are a lot of
other ways to fail besides an optional being nil (status code came back as
error, unexpected data, connection timeout).

I'd like to see some real-world examples of this before we did anything with
it.

Jordan

On Apr 6, 2016, at 8:00, Sean Heber via swift-evolution > <swift-evolution@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. :stuck_out_tongue:

l8r
Sean

On Apr 6, 2016, at 9:46 AM, Erica Sadun via swift-evolution > <swift-evolution@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@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

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