[Pitch] Brace omission for single-statement n-ary closures


(Marc Prud'hommeaux) #1

Everyone loves this syntax:

  let pos = [1, -2, 3].map(abs)

This syntax not so much:

  let neg = [1, -2, 3].map({ -abs($0) })

For single-statement closures, ordered parameters could be referenced in an autoclosure-like way using a placeholder for the parameter:

  let neg = [1, -2, 3].map(-abs(_))

The behavior would be the same as the current $0, $1, ... shorthand argument names to inline closures except that parameter re-ordering wouldn't be allowed (the first instance of _ would be $0, the second would be $1, etc). So the following three lines would all be compiled the same:

  let reversed = words.sort( { $0 > $1 } )
  let reversed = words.sort(_ > _)
  let reversed = words.sort(>)

As with autoclosures, noescape rules would be enforced since there wouldn't be any way to specify that self is weak or unowned. So while the following requires use of self to make capture semantics explicit:

  words.lazy.filter({ $0.hashValue == self.hashValue })

These would both raise errors:

  words.lazy.filter(_.hashValue == hashValue)
  words.lazy.filter(_.hashValue == self.hashValue)

Nested closures would also behave the same as with dollar-shorthand:

  let words = ["abc", "def", "hij"]
  let ascii = words.flatMap({ $0.unicodeScalars.filter({ $0.isASCII }) })
  let ascii = words.flatMap(_.unicodeScalars.filter(_.isASCII))

Another example:

  let words: [String?] = ["Hey", "you", nil, "guys"]
  let exclaim = words.flatMap({ $0 }).map({ $0.uppercaseString }).reduce("", combine: { $0+"! "+$1 })
  let exclaim = words.flatMap(_).map(_.uppercaseString).reduce("", combine: _+"! "+_)

Why underscore and not another character like a lone "$" or "."? The meaning of the underscore character as an anonymous ordered placeholder would be consistent with other usages throughout the Swift language. Examples:

  let (_, two, _) = (1, 2.0, "Three") // anonymous tuple property
  words.reduce("", combine: { (x, _) in x }) // anonymous closure parameter
  let advancer = Int.advancedBy(_:limit:) // anonymous function argument
  switch "X" as String? {
  case .Some(_): print("something") // anonymous enum value
  case .None: print("nothing")
  }

And since underscore currently isn't allowed on the right-hand side of an expression, this syntax enhancement shouldn't break any existing code. Lastly, this same syntax for anonymous arguments is used in Scala, so it would already be familiar to a large developer base.

Any yeas or nays?

  -Marc


(Brent Royal-Gordon) #2

For single-statement closures, ordered parameters could be referenced in an autoclosure-like way using a placeholder for the parameter:

  let neg = [1, -2, 3].map(-abs(_))

I'm a big fan of this, or something like this, but the core team has rejected proposals like this before. They apparently believe that it would be difficult to tell how much of the expression you intended to make a closure of:

  let neg = [1, -2, 3].map(-{ abs($0) })
  let neg = [1, -2, 3].map({ -abs($0) })
  let neg = { [1, -2, 3].map(-abs($0)) }

And believe that this syntax is limited in important ways, like being unable to reorder parameters. They think that closures with $N are more explicit and flexible while only requiring a little bit more code.

···

--
Brent Royal-Gordon
Architechies


(Patrick Smith) #3

I know there was more to what you were suggesting, but can I suggest removing the parentheses:

  let neg = [1, -2, 3].map{ -abs($0) }

  let ascii = words.flatMap{ $0.unicodeScalars.filter{ $0.isASCII } }

Patrick

···

On 25 May 2016, at 5:46 AM, Marc Prud'hommeaux via swift-evolution <swift-evolution@swift.org> wrote:

Everyone loves this syntax:

  let pos = [1, -2, 3].map(abs)

This syntax not so much:

  let neg = [1, -2, 3].map({ -abs($0) })


(Charlie Monroe) #4

I'm a big fan of this, or something like this, but the core team has rejected proposals like this before. They apparently believe that it would be difficult to tell how much of the expression you intended to make a closure of:

  let neg = [1, -2, 3].map(-{ abs($0) })
  let neg = [1, -2, 3].map({ -abs($0) })
  let neg = { [1, -2, 3].map(-abs($0)) }

I believe that in Scala (which has this feature), the entire content of the parenthesis is the statement and you need to use the placeholder (_ in Scala, $0 in Swift) to indicate it's a closure and not something else.

I've never been a fan of this as I don't think it improves readability, mostly when you start nesting stuff like:

array.map($0.tranform($0.uppercaseString))

which seems incredibly confusing.

···

And believe that this syntax is limited in important ways, like being unable to reorder parameters. They think that closures with $N are more explicit and flexible while only requiring a little bit more code.

--
Brent Royal-Gordon
Architechies

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