[Pre-Draft] Nil-coalescing and errors


(Thorsten Seitz) #1

You are certainly right.

I only wonder whether you really want to repeat Error() all over, possibly with aString etc. as argument.

If not extracting the logic of parsing + throwing into a function or method (e.g. toInt) which throws the correct error for your domain, possibly enriched with some arguments, still makes more sense than using ???.

-Thorsten

···

Am 07. April 2016 um 15:23 schrieb Yuta Koshizawa koher@koherent.org:

Why not have throwing variants of the string and int methods (in
addition to those returning an optional)?

Then you could simply write:

// 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
}

-Thorsten

Am 07. April 2016 um 14:01 schrieb Yuta Koshizawa via swift-evolution > > swift-evolution@swift.org:

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 ???(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(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 ??(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


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

Of course we can implement a library with throws by changing those
properties to methods. However we can think about similar cases.

do {
 let foo: Foo = try foo(
 a: Int(aString) ??? Error(),
 b: Int(bString) ??? Error(),
 c: Int(cString) ??? Error()
 )
} catch _ {
 // Error handling
}

Dictionary also returns optionals. We have already had various
functions/methods which returns optionals.

– Yuta

2016-04-07 21:34 GMT+09:00 Thorsten Seitz via swift-evolution
swift-evolution@swift.org:


(Brent Royal-Gordon) #2

I only wonder whether you really want to repeat Error() all over, possibly with `aString` etc. as argument.

`Error()`, no. `SpimsterKitError.invalidWicketField("a"`), yes, because even if `Int.init(_:)` threw *an* error, it wouldn't throw *your* error.

···

--
Brent Royal-Gordon
Architechies


(Thorsten Seitz) #3

That's why I thought that in a real use case that logic would likely be extracted.

-Thorsten

···

Am 08.04.2016 um 11:59 schrieb Brent Royal-Gordon <brent@architechies.com>:

I only wonder whether you really want to repeat Error() all over, possibly with `aString` etc. as argument.

`Error()`, no. `SpimsterKitError.invalidWicketField("a"`), yes, because even if `Int.init(_:)` threw *an* error, it wouldn't throw *your* error.


(Yuta Koshizawa) #4

I only wonder whether you really want to repeat Error() all over,
possibly with `aString` etc. as argument.

What I really want is the postfix version of `???` as I wrote in my
first post in this thread.

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.

And also

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.

Then it becomes something like the following.

do {
    let foo: Foo = try foo(
        b: Int(bString)|?,
        c: Int(cString)|?
    )
} catch _ {
    // Error handling
}

I think the infix version is also useful when we actually want to
specify the types of errors.

Swift provides two ways of error handling: optionals and do/try/catch.
I think it lacks a way to handle multiple optionals easily in some
cases. Someone uses applicative styles for it.

let foo: Foo? = curry(Foo.init) <^> Int(aString) <*> Int(bString) <*>
Int(cString)

But I think it is unreasonable to expect all programmers to understand
and master it. So I want the postfix `???` or `|?`.

-- Yuta

···

a: Int(aString)|?,

2016-04-08 23:47 GMT+09:00 Thorsten Seitz <tseitz42@icloud.com>:

Am 08.04.2016 um 11:59 schrieb Brent Royal-Gordon <brent@architechies.com>:

I only wonder whether you really want to repeat Error() all over, possibly with `aString` etc. as argument.

`Error()`, no. `SpimsterKitError.invalidWicketField("a"`), yes, because even if `Int.init(_:)` threw *an* error, it wouldn't throw *your* error.

That's why I thought that in a real use case that logic would likely be extracted.

-Thorsten


(Radek Pietruszewski) #5

Just FWIW:

let foo: Foo? = curry(Foo.init) <^> Int(aString) <*> Int(bString) <*>
Int(cString)

But I think it is unreasonable to expect all programmers to understand
and master it. So I want the postfix `???` or `|?`.

I agree. But I would also say that deserializing from JSON is complex enough of a problem to warrant a domain-specific solution. There’s a ton of them, of course, but for example, with Freddy, it would look something like:

extension Person: JSONDecodable {
    init(json j: JSON) throws {
        firstName = try j.string(“firstName”)
        lastName = try j.string(“lastName”)
        age = try j.int(“age”)
    }
}

let person = try Person(json: json)

Even with the proposed `???`, a domain-specific solution would be arguably better. So I just don’t consider that a compelling use case.

— Radek

···

On 09 Apr 2016, at 16:56, Yuta Koshizawa via swift-evolution <swift-evolution@swift.org> wrote:

I only wonder whether you really want to repeat Error() all over,
possibly with `aString` etc. as argument.

What I really want is the postfix version of `???` as I wrote in my
first post in this thread.

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.

And also

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.

Then it becomes something like the following.

do {
   let foo: Foo = try foo(
       a: Int(aString)|?,
       b: Int(bString)|?,
       c: Int(cString)|?
   )
} catch _ {
   // Error handling
}

I think the infix version is also useful when we actually want to
specify the types of errors.

Swift provides two ways of error handling: optionals and do/try/catch.
I think it lacks a way to handle multiple optionals easily in some
cases. Someone uses applicative styles for it.

let foo: Foo? = curry(Foo.init) <^> Int(aString) <*> Int(bString) <*>
Int(cString)

But I think it is unreasonable to expect all programmers to understand
and master it. So I want the postfix `???` or `|?`.

-- Yuta

2016-04-08 23:47 GMT+09:00 Thorsten Seitz <tseitz42@icloud.com>:

Am 08.04.2016 um 11:59 schrieb Brent Royal-Gordon <brent@architechies.com>:

I only wonder whether you really want to repeat Error() all over, possibly with `aString` etc. as argument.

`Error()`, no. `SpimsterKitError.invalidWicketField("a"`), yes, because even if `Int.init(_:)` threw *an* error, it wouldn't throw *your* error.

That's why I thought that in a real use case that logic would likely be extracted.

-Thorsten

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


(Yuta Koshizawa) #6

Hi.

Decoding a JSON is just an example. As I replied to Thorsten, we can
think about various cases we want to unwrap multiple optionals at
once.

This is another example (with the notation `|?` and the postfix
version I proposed instead of the infix `???`).

do {
  let sum = try Int(aString)|? + Int(bString)|?
} catch _ {
  // Error handling
}

With optional binding, we need to write something like the following.
It needs additional assignments (bindings) to `a` and `b`.

if let a = Int(aString), b = Int(bString) {
  let sum = a + b
} else {
  // Error handling
}

`Dictionary`'s subscript also returns an optional. Or simply we may
have optional values as properties. We have a lot of optional values
and cases in which we want to pass their unwrapped values directly to
functions. It seems endless to prepare domain-specific solutions for
all of them.

-- Yuta

···

2016-04-11 3:33 GMT+09:00 Radosław Pietruszewski <radexpl@gmail.com>:

Just FWIW:

let foo: Foo? = curry(Foo.init) <^> Int(aString) <*> Int(bString) <*>
Int(cString)

But I think it is unreasonable to expect all programmers to understand
and master it. So I want the postfix `???` or `|?`.

I agree. But I would also say that deserializing from JSON is complex enough
of a problem to warrant a domain-specific solution. There’s a ton of them,
of course, but for example, with Freddy, it would look something like:

extension Person: JSONDecodable {
    init(json j: JSON) throws {
        firstName = try j.string(“firstName”)
        lastName = try j.string(“lastName”)
        age = try j.int(“age”)
    }
}

let person = try Person(json: json)

Even with the proposed `???`, a domain-specific solution would be arguably
better. So I just don’t consider that a compelling use case.

— Radek

On 09 Apr 2016, at 16:56, Yuta Koshizawa via swift-evolution > <swift-evolution@swift.org> wrote:

I only wonder whether you really want to repeat Error() all over,
possibly with `aString` etc. as argument.

What I really want is the postfix version of `???` as I wrote in my
first post in this thread.

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.

And also

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.

Then it becomes something like the following.

do {
   let foo: Foo = try foo(
       a: Int(aString)|?,
       b: Int(bString)|?,
       c: Int(cString)|?
   )
} catch _ {
   // Error handling
}

I think the infix version is also useful when we actually want to
specify the types of errors.

Swift provides two ways of error handling: optionals and do/try/catch.
I think it lacks a way to handle multiple optionals easily in some
cases. Someone uses applicative styles for it.

let foo: Foo? = curry(Foo.init) <^> Int(aString) <*> Int(bString) <*>
Int(cString)

But I think it is unreasonable to expect all programmers to understand
and master it. So I want the postfix `???` or `|?`.

-- Yuta

2016-04-08 23:47 GMT+09:00 Thorsten Seitz <tseitz42@icloud.com>:

Am 08.04.2016 um 11:59 schrieb Brent Royal-Gordon <brent@architechies.com>:

I only wonder whether you really want to repeat Error() all over, possibly
with `aString` etc. as argument.

`Error()`, no. `SpimsterKitError.invalidWicketField("a"`), yes, because even
if `Int.init(_:)` threw *an* error, it wouldn't throw *your* error.

That's why I thought that in a real use case that logic would likely be
extracted.

-Thorsten

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


(Thorsten Seitz) #7

Actually I find this much more readable than the version above which mixes error handling (|?) and the business logic (a + b), making the business logic more difficult to understand. Using meaningful names instead of `a` and `b` will increase readability even more.

-Thorsten

···

Am 12.04.2016 um 01:01 schrieb Yuta Koshizawa <koher@koherent.org>:

Hi.

Decoding a JSON is just an example. As I replied to Thorsten, we can
think about various cases we want to unwrap multiple optionals at
once.

This is another example (with the notation `|?` and the postfix
version I proposed instead of the infix `???`).

do {
 let sum = try Int(aString)|? + Int(bString)|?
} catch _ {
 // Error handling
}

With optional binding, we need to write something like the following.
It needs additional assignments (bindings) to `a` and `b`.

if let a = Int(aString), b = Int(bString) {
 let sum = a + b
} else {
 // Error handling
}