[Pitch] Change location of 'try' for infix operators


(Karl) #1

You might expect this code to work:

func aFunction() -> Int? { return 5 }
func bFunction() throws -> Int { return 4 }

let value = aFunction() ?? try bFunction() // ERROR: Operator can throw but expression is not marked with a ‘try'
print(value)

Instead, you must put the ‘try’ before the entire expression:

let value = try aFunction() ?? bFunction()

This is awkward, since aFunction() doesn’t throw.
I propose we change the grammar to allow the first example and disallow the second, consistent with the idea that throwing calls are ‘marked’ by using the try keyword.

Karl


(Benjamin Spratling) #2

Howdy,
The error message is not saying that aFunction throws, it says “??" might throw. After all, you supplied a ()rethrows->(Int) to it as its second argument, which is wrapping a ()throws->Int, “bFunction()"
?? and && and || wrap the trailing expression in an @autoclosure.

I am a little surprised two “try” are not required. This would be my expectation:

let value = try aFunction() ?? try bFunction()

but, using try to the right of a non-assignment operator is not allowed.

This, however, is not disallowed:

let value = try aFunction() ?? (try bFunction())

The purpose of the @autoclosure is to make developers forget they need to write a closure, and it apparently worked for you.

-Ben Spratling

···

On Oct 11, 2016, at 1:16 AM, Karl via swift-evolution <swift-evolution@swift.org> wrote:

You might expect this code to work:

func aFunction() -> Int? { return 5 }
func bFunction() throws -> Int { return 4 }

let value = aFunction() ?? try bFunction() // ERROR: Operator can throw but expression is not marked with a ‘try'
print(value)

Instead, you must put the ‘try’ before the entire expression:

let value = try aFunction() ?? bFunction()

This is awkward, since aFunction() doesn’t throw.
I propose we change the grammar to allow the first example and disallow the second, consistent with the idea that throwing calls are ‘marked’ by using the try keyword.

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


(Ben Rimmington) #3

The `??` function rethrows an error from its rhs operand.

  @_transparent
  public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T)
      rethrows -> T {
    switch optional {
    case .some(let value):
      return value
    case .none:
      return try defaultValue()
    }
  }

<https://github.com/apple/swift/blob/5d3a7f7c230ae238c848a06f58b58c7e68fb5ed0/stdlib/public/core/Optional.swift#L415-L424>

-- Ben

···

On 11 Oct 2016, at 07:16, Karl wrote:

You might expect this code to work:

func aFunction() -> Int? { return 5 }
func bFunction() throws -> Int { return 4 }

let value = aFunction() ?? try bFunction() // ERROR: Operator can throw but expression is not marked with a ‘try'
print(value)

Instead, you must put the ‘try’ before the entire expression:

let value = try aFunction() ?? bFunction()

This is awkward, since aFunction() doesn’t throw.
I propose we change the grammar to allow the first example and disallow the second, consistent with the idea that throwing calls are ‘marked’ by using the try keyword.


(Karl) #4

Yeah, I understand the reason for it, but I still think we should change it so you write the ‘try’ before the part which actually throws. Perhaps the rule should be something more general around rethrowing autoclosures?

After all, I thought that was the entire point of the ‘try’ keyword. The compiler doesn't really need it (it already knows what throws and what doesn’t), but it helps humans to mark where the throwing stuff happens.

Karl

···

On 11 Oct 2016, at 08:49, Benjamin Spratling <bspratling@mac.com> wrote:

Howdy,
The error message is not saying that aFunction throws, it says “??" might throw. After all, you supplied a ()rethrows->(Int) to it as its second argument, which is wrapping a ()throws->Int, “bFunction()"
?? and && and || wrap the trailing expression in an @autoclosure.

I am a little surprised two “try” are not required. This would be my expectation:

let value = try aFunction() ?? try bFunction()

but, using try to the right of a non-assignment operator is not allowed.

This, however, is not disallowed:

let value = try aFunction() ?? (try bFunction())

The purpose of the @autoclosure is to make developers forget they need to write a closure, and it apparently worked for you.

-Ben Spratling


(John McCall) #5

I agree that you should be able to place the try inside the autoclosure and have that propagate out. This doesn't need evolution discussion; please file a bug.

John.

···

On Oct 11, 2016, at 12:04 AM, Karl via swift-evolution <swift-evolution@swift.org> wrote:

On 11 Oct 2016, at 08:49, Benjamin Spratling <bspratling@mac.com <mailto:bspratling@mac.com>> wrote:

Howdy,
The error message is not saying that aFunction throws, it says “??" might throw. After all, you supplied a ()rethrows->(Int) to it as its second argument, which is wrapping a ()throws->Int, “bFunction()"
?? and && and || wrap the trailing expression in an @autoclosure.

I am a little surprised two “try” are not required. This would be my expectation:

let value = try aFunction() ?? try bFunction()

but, using try to the right of a non-assignment operator is not allowed.

This, however, is not disallowed:

let value = try aFunction() ?? (try bFunction())

The purpose of the @autoclosure is to make developers forget they need to write a closure, and it apparently worked for you.

-Ben Spratling

Yeah, I understand the reason for it, but I still think we should change it so you write the ‘try’ before the part which actually throws. Perhaps the rule should be something more general around rethrowing autoclosures?

After all, I thought that was the entire point of the ‘try’ keyword. The compiler doesn't really need it (it already knows what throws and what doesn’t), but it helps humans to mark where the throwing stuff happens.


(Karl) #6

Done. https://bugs.swift.org/browse/SR-2963.

···

On 14 Oct 2016, at 22:42, John McCall <rjmccall@apple.com> wrote:

On Oct 11, 2016, at 12:04 AM, Karl via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On 11 Oct 2016, at 08:49, Benjamin Spratling <bspratling@mac.com <mailto:bspratling@mac.com>> wrote:

Howdy,
The error message is not saying that aFunction throws, it says “??" might throw. After all, you supplied a ()rethrows->(Int) to it as its second argument, which is wrapping a ()throws->Int, “bFunction()"
?? and && and || wrap the trailing expression in an @autoclosure.

I am a little surprised two “try” are not required. This would be my expectation:

let value = try aFunction() ?? try bFunction()

but, using try to the right of a non-assignment operator is not allowed.

This, however, is not disallowed:

let value = try aFunction() ?? (try bFunction())

The purpose of the @autoclosure is to make developers forget they need to write a closure, and it apparently worked for you.

-Ben Spratling

Yeah, I understand the reason for it, but I still think we should change it so you write the ‘try’ before the part which actually throws. Perhaps the rule should be something more general around rethrowing autoclosures?

After all, I thought that was the entire point of the ‘try’ keyword. The compiler doesn't really need it (it already knows what throws and what doesn’t), but it helps humans to mark where the throwing stuff happens.

I agree that you should be able to place the try inside the autoclosure and have that propagate out. This doesn't need evolution discussion; please file a bug.

John.