Proposal: Extend Optional Chaining to Function, Initializer, and Subscript Parameters


(Liam Stevenson) #1

Optional chaining is one of the great, useful features of Swift. It can be used “for querying and calling properties, methods, and subscripts on an optional that might currently be nil,” to quote Apple's "The Swift Programming Language.” However, often it is necessary to call a function, subscript, or initializer conditionally based on if one or more parameters are nil. The proposed solution is to allow a question mark (?) to be placed after an optional value wished to be used as a parameter. Then, the function, initializer, or subscript will be called if and only if the parameter's value is not nil. If it has a return type, it will return an optional, which will be nil if the parameter is nil.

Old way (with seemingly unnecessary if statement considering the flexibility provided by optional chaining):
  var arr = ["apples", "oranges", "pears", "bananas"]
  let index: Int? = 2

  var removedElement: String?
  if let index = index {
      removedElement = arr.removeAtIndex(index) //sets removedElement to "pears"
  }
Using this proposal:
  var arr = ["apples", "oranges", "pears", "bananas"]
  let index: Int? = 2

  var removedElement: String?
  removedElement = arr.removeAtIndex(index?) //sets removedElement to “pears"
Another similar example:
Old way:
  var arr = ["apples", "oranges", "pears", "bananas"]
  let index: Int? = nil

  var removedElement: String?
  if let index = index {
      removedElement = arr.removeAtIndex(index) //never called
  }
Using this proposal:
  var arr = ["apples", "oranges", "pears", "bananas"]
  let index: Int? = nil

  var removedElement: String?
  removedElement = arr.removeAtIndex(index?) //removeAtIndex is never called, and removedElement is set to nil

What does everyone think of this proposal? It is additive so it will not break any existing code, and in the future it will provide conciseness and clarity since the syntax is similar to the existing optional chaining syntax.

View the full proposal on GitHub here: https://github.com/liam923/swift-evolution/blob/master/proposals/NNNN-extend-optional-chaining-to-function-initializer-and-subscript-parameters.md

Liam


(Jordan Rose) #2

Hi, Liam. The particular issue we’ve seen when exploring this feature is that it’s unclear whether or not other arguments get evaluated:

print(a.foo()?, b.bar()?, c.baz()?)

If b.bar() turns out to be nil, does c.baz() still get evaluated?

(I’m pretty sure the right answer is “yes”, but it’s still something that should be addressed explicitly.)

The added complexity and the existence of Optional.map led us not to pursue this direction in the past.

Jordan

···

On Jul 12, 2016, at 07:16, Liam Stevenson via swift-evolution <swift-evolution@swift.org> wrote:

Optional chaining is one of the great, useful features of Swift. It can be used “for querying and calling properties, methods, and subscripts on an optional that might currently be nil,” to quote Apple's "The Swift Programming Language.” However, often it is necessary to call a function, subscript, or initializer conditionally based on if one or more parameters are nil. The proposed solution is to allow a question mark (?) to be placed after an optional value wished to be used as a parameter. Then, the function, initializer, or subscript will be called if and only if the parameter's value is not nil. If it has a return type, it will return an optional, which will be nil if the parameter is nil.

Old way (with seemingly unnecessary if statement considering the flexibility provided by optional chaining):
  var arr = ["apples", "oranges", "pears", "bananas"]
  let index: Int? = 2

  var removedElement: String?
  if let index = index {
      removedElement = arr.removeAtIndex(index) //sets removedElement to "pears"
  }
Using this proposal:
  var arr = ["apples", "oranges", "pears", "bananas"]
  let index: Int? = 2

  var removedElement: String?
  removedElement = arr.removeAtIndex(index?) //sets removedElement to “pears"
Another similar example:
Old way:
  var arr = ["apples", "oranges", "pears", "bananas"]
  let index: Int? = nil

  var removedElement: String?
  if let index = index {
      removedElement = arr.removeAtIndex(index) //never called
  }
Using this proposal:
  var arr = ["apples", "oranges", "pears", "bananas"]
  let index: Int? = nil

  var removedElement: String?
  removedElement = arr.removeAtIndex(index?) //removeAtIndex is never called, and removedElement is set to nil

What does everyone think of this proposal? It is additive so it will not break any existing code, and in the future it will provide conciseness and clarity since the syntax is similar to the existing optional chaining syntax.

View the full proposal on GitHub here: https://github.com/liam923/swift-evolution/blob/master/proposals/NNNN-extend-optional-chaining-to-function-initializer-and-subscript-parameters.md

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


(David Hart) #3

We can already do this nicely with map and flatMap:

var removedElement = index.map { arr.removeAtIndex($0) }

···

On 12 Jul 2016, at 16:16, Liam Stevenson via swift-evolution <swift-evolution@swift.org> wrote:

Optional chaining is one of the great, useful features of Swift. It can be used “for querying and calling properties, methods, and subscripts on an optional that might currently be nil,” to quote Apple's "The Swift Programming Language.” However, often it is necessary to call a function, subscript, or initializer conditionally based on if one or more parameters are nil. The proposed solution is to allow a question mark (?) to be placed after an optional value wished to be used as a parameter. Then, the function, initializer, or subscript will be called if and only if the parameter's value is not nil. If it has a return type, it will return an optional, which will be nil if the parameter is nil.

Old way (with seemingly unnecessary if statement considering the flexibility provided by optional chaining):
  var arr = ["apples", "oranges", "pears", "bananas"]
  let index: Int? = 2

  var removedElement: String?
  if let index = index {
      removedElement = arr.removeAtIndex(index) //sets removedElement to "pears"
  }
Using this proposal:
  var arr = ["apples", "oranges", "pears", "bananas"]
  let index: Int? = 2

  var removedElement: String?
  removedElement = arr.removeAtIndex(index?) //sets removedElement to “pears"
Another similar example:
Old way:
  var arr = ["apples", "oranges", "pears", "bananas"]
  let index: Int? = nil

  var removedElement: String?
  if let index = index {
      removedElement = arr.removeAtIndex(index) //never called
  }
Using this proposal:
  var arr = ["apples", "oranges", "pears", "bananas"]
  let index: Int? = nil

  var removedElement: String?
  removedElement = arr.removeAtIndex(index?) //removeAtIndex is never called, and removedElement is set to nil

What does everyone think of this proposal? It is additive so it will not break any existing code, and in the future it will provide conciseness and clarity since the syntax is similar to the existing optional chaining syntax.

View the full proposal on GitHub here: https://github.com/liam923/swift-evolution/blob/master/proposals/NNNN-extend-optional-chaining-to-function-initializer-and-subscript-parameters.md

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


(Goffredo Marocchi) #4

I did not mind sending messages to nil being essentially a NoOp so calling map on an optional and have the function only execute if the value included inside the optional is not nil is kind of reminding of that ;). Although it is a lot wordier and not really in line with how we force people to deal with nil in then general case --> you better unwrap or use optional chaining.

It feels almost like this use of map is something that should come in one of those web adverts "See the 10 things Apple does not want you to do with Optionals, but made these coders £40,000" ;).

···

Sent from my iPhone

On 12 Jul 2016, at 17:37, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

Hi, Liam. The particular issue we’ve seen when exploring this feature is that it’s unclear whether or not other arguments get evaluated:

print(a.foo()?, b.bar()?, c.baz()?)

If b.bar() turns out to be nil, does c.baz() still get evaluated?

(I’m pretty sure the right answer is “yes”, but it’s still something that should be addressed explicitly.)

The added complexity and the existence of Optional.map led us not to pursue this direction in the past.

Jordan

On Jul 12, 2016, at 07:16, Liam Stevenson via swift-evolution <swift-evolution@swift.org> wrote:

Optional chaining is one of the great, useful features of Swift. It can be used “for querying and calling properties, methods, and subscripts on an optional that might currently be nil,” to quote Apple's "The Swift Programming Language.” However, often it is necessary to call a function, subscript, or initializer conditionally based on if one or more parameters are nil. The proposed solution is to allow a question mark (?) to be placed after an optional value wished to be used as a parameter. Then, the function, initializer, or subscript will be called if and only if the parameter's value is not nil. If it has a return type, it will return an optional, which will be nil if the parameter is nil.

Old way (with seemingly unnecessary if statement considering the flexibility provided by optional chaining):
  var arr = ["apples", "oranges", "pears", "bananas"]
  let index: Int? = 2

  var removedElement: String?
  if let index = index {
      removedElement = arr.removeAtIndex(index) //sets removedElement to "pears"
  }
Using this proposal:
  var arr = ["apples", "oranges", "pears", "bananas"]
  let index: Int? = 2

  var removedElement: String?
  removedElement = arr.removeAtIndex(index?) //sets removedElement to “pears"
Another similar example:
Old way:
  var arr = ["apples", "oranges", "pears", "bananas"]
  let index: Int? = nil

  var removedElement: String?
  if let index = index {
      removedElement = arr.removeAtIndex(index) //never called
  }
Using this proposal:
  var arr = ["apples", "oranges", "pears", "bananas"]
  let index: Int? = nil

  var removedElement: String?
  removedElement = arr.removeAtIndex(index?) //removeAtIndex is never called, and removedElement is set to nil

What does everyone think of this proposal? It is additive so it will not break any existing code, and in the future it will provide conciseness and clarity since the syntax is similar to the existing optional chaining syntax.

View the full proposal on GitHub here: https://github.com/liam923/swift-evolution/blob/master/proposals/NNNN-extend-optional-chaining-to-function-initializer-and-subscript-parameters.md

Liam
_______________________________________________
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


(Liam Stevenson) #5

I'm glad that you agree with me. However, my proposal has been marked as out of scope for swift 3.0, so we'll have to wait a month or so to bring this back up again.

···

On Jul 13, 2016, at 10:24 AM, charles@charlesism.com <charlesism.com@gmail.com> wrote:

+1

I'm in favour of this, or something similar. It currently requires a bit more effort to deal with an optional that appears as an argument. It would be a bit nicer to make decisions about control flow when the amount of effort your choices require aren't affected by things like this.

Sent from my iPhone

On Jul 12, 2016, at 7:16 AM, Liam Stevenson via swift-evolution <swift-evolution@swift.org> wrote:

Optional chaining is one of the great, useful features of Swift. It can be used “for querying and calling properties, methods, and subscripts on an optional that might currently be nil,” to quote Apple's "The Swift Programming Language.” However, often it is necessary to call a function, subscript, or initializer conditionally based on if one or more parameters are nil. The proposed solution is to allow a question mark (?) to be placed after an optional value wished to be used as a parameter. Then, the function, initializer, or subscript will be called if and only if the parameter's value is not nil. If it has a return type, it will return an optional, which will be nil if the parameter is nil.

Old way (with seemingly unnecessary if statement considering the flexibility provided by optional chaining):
  var arr = ["apples", "oranges", "pears", "bananas"]
  let index: Int? = 2

  var removedElement: String?
  if let index = index {
      removedElement = arr.removeAtIndex(index) //sets removedElement to "pears"
  }
Using this proposal:
  var arr = ["apples", "oranges", "pears", "bananas"]
  let index: Int? = 2

  var removedElement: String?
  removedElement = arr.removeAtIndex(index?) //sets removedElement to “pears"
Another similar example:
Old way:
  var arr = ["apples", "oranges", "pears", "bananas"]
  let index: Int? = nil

  var removedElement: String?
  if let index = index {
      removedElement = arr.removeAtIndex(index) //never called
  }
Using this proposal:
  var arr = ["apples", "oranges", "pears", "bananas"]
  let index: Int? = nil

  var removedElement: String?
  removedElement = arr.removeAtIndex(index?) //removeAtIndex is never called, and removedElement is set to nil

What does everyone think of this proposal? It is additive so it will not break any existing code, and in the future it will provide conciseness and clarity since the syntax is similar to the existing optional chaining syntax.

View the full proposal on GitHub here: https://github.com/liam923/swift-evolution/blob/master/proposals/NNNN-extend-optional-chaining-to-function-initializer-and-subscript-parameters.md

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


(Liam Stevenson) #6

I realize that it can be done in that way, and also in others, such as using if statements. The point of this proposal is for conciseness and simplicity. Also, doing it that way would become extensive if there were more than one parameters.

···

On Jul 12, 2016, at 12:14 PM, David Hart <david@hartbit.com> wrote:

We can already do this nicely with map and flatMap:

var removedElement = index.map { arr.removeAtIndex($0) }

On 12 Jul 2016, at 16:16, Liam Stevenson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Optional chaining is one of the great, useful features of Swift. It can be used “for querying and calling properties, methods, and subscripts on an optional that might currently be nil,” to quote Apple's "The Swift Programming Language.” However, often it is necessary to call a function, subscript, or initializer conditionally based on if one or more parameters are nil. The proposed solution is to allow a question mark (?) to be placed after an optional value wished to be used as a parameter. Then, the function, initializer, or subscript will be called if and only if the parameter's value is not nil. If it has a return type, it will return an optional, which will be nil if the parameter is nil.

Old way (with seemingly unnecessary if statement considering the flexibility provided by optional chaining):
  var arr = ["apples", "oranges", "pears", "bananas"]
  let index: Int? = 2

  var removedElement: String?
  if let index = index {
      removedElement = arr.removeAtIndex(index) //sets removedElement to "pears"
  }
Using this proposal:
  var arr = ["apples", "oranges", "pears", "bananas"]
  let index: Int? = 2

  var removedElement: String?
  removedElement = arr.removeAtIndex(index?) //sets removedElement to “pears"
Another similar example:
Old way:
  var arr = ["apples", "oranges", "pears", "bananas"]
  let index: Int? = nil

  var removedElement: String?
  if let index = index {
      removedElement = arr.removeAtIndex(index) //never called
  }
Using this proposal:
  var arr = ["apples", "oranges", "pears", "bananas"]
  let index: Int? = nil

  var removedElement: String?
  removedElement = arr.removeAtIndex(index?) //removeAtIndex is never called, and removedElement is set to nil

What does everyone think of this proposal? It is additive so it will not break any existing code, and in the future it will provide conciseness and clarity since the syntax is similar to the existing optional chaining syntax.

View the full proposal on GitHub here: https://github.com/liam923/swift-evolution/blob/master/proposals/NNNN-extend-optional-chaining-to-function-initializer-and-subscript-parameters.md

Liam
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Garth Snyder) #7

Liam: ...my proposal has been marked as out of scope for swift 3.0, so we'll have to wait a month or so to bring this back up again.

That seems reasonable, but I _would_ like to see this come up for discussion.

IMHO, cases such as

let token = authorize(user?, password?, operation?)

are a lot more compelling than any example involving a single optional. As pointed out, a single optional is pretty easy to deal with in various ways. But checking multiple values gets clumsy:

var token: AuthToken?
if let user = user, password = password, operation = operation {
    token = authorize(user, password, operation)
}

It bothers me that the result has to be declared separately and then reset. Nested maps are even worse.

Garth