Understanding Shorthand Closure Syntax for Map Function in Swift

Understanding shorthand closure syntax for map function in Swift

Distinguish parentheses () from curly braces {}.

In a sense, only the parentheses version is "real", because, after all, that is what a function call requires. In the parentheses when you call map, you put a function. It may be a function reference (i.e. the name of a function):

    let arr = [1,2,3]
func double(i:Int) -> Int {return i*2}
let arr2 = arr.map(double)

Or it can be an anonymous function, meaning a function body in curly braces:

    let arr = [1,2,3]
let arr2 = arr.map({$0*2})

But in that case, and that case only, you can (as a shortcut) use the "trailing closure" syntax instead:

    let arr = [1,2,3]
let arr2 = arr.map(){$0*2}

But since map takes no other parameters, you can then also omit the parentheses — the only situation in Swift where you can call a function without parentheses:

    let arr = [1,2,3]
let arr2 = arr.map{$0*2}

Swift syntax explanation with compact map

In the call .compactMap(prepareAttributes), you pass in the function, prepareAttributes to compactMap as a closure. Since prepareAttributes takes a single input argument whose type matches the closure variable of compactMap, the compiler can automatically infer that it needs to pass $0 to prepareAttributes.

So essentially, .compactMap(prepareAttributes) is shorthand for

.compactMap {prepareAttributes(attributes: $0) }

A simple example of the same behaviour with map that is quite often used is to map over a type that you then pass into an init, which you could write as .map { MyType(input: $0) } or simplify to .map(MyType.init).

struct MyInt {
let value: Int

init(value: Int) {
self.value = value
}
}

let ints = [1,2,3]
let myInts = ints.map(MyInt.init) // same as `ints.map { MyInt(value: $0) }

What does $0 and $1 mean in Swift Closures?

$0 is the first parameter passed into the closure. $1 is the second parameter, etc. That closure you showed is shorthand for:

let sortedNumbers = numbers.sort { (firstObject, secondObject) in 
return firstObject > secondObject
}

Why don't shorthand argument names work in this Swift closure?

It depends on the number of parameters.

For example,

func test( closure: (Int,Int,Int) -> Void ){
// do something
}

To make test works as you expect, you must specify $2 ( 3rd argument ). The compiler will infer to the values inside tuple, otherwise it will infer to the tuple itself.

If you don't specify $number that match the number of parameters. For example, only specify $1, will make compile error.

// work as expected ( infer to int )
test{
print($2)
}
test{
print($1+$2)
}
test{
print($0+$1+$2)
}

// not work ( infer to tuple )
test{
print($0)
}

// not work ( cannot infer and compile error )
test{
print($1)
}

There is a question relate to this question. Why is the shorthand argument name $0 returning a tuple of all parameters?

Closures shorthand syntax in Swift 3

The rationale is explained in the proposal that removed it. It created subtle problems in the second case, but not in the first. And, as in the case of most "why does the compiler not throw an error here," the answer is "because it doesn't prevent it." It doesn't always mean there was a deep intention at work. Sometimes it works because it wasn't prevented from working.

But, it's also true that this isn't actually a special case in the first example. This works, too, with labels:

func f(x: Int, y: Int) -> Int { ... }
[(1, 2)].map(f)

In the second example, that didn't used to work because it runs up against the fact that tuple labels are not part of the type, so there isn't currently a way to express it correctly (this being one of the subtle problems). See:

  • Remove type system significance of function argument labels
  • Closure Parameter Names and Labels

It is important to note that this is a feature common in functional languages, but Swift is not a functional language, doesn't intend to be one, and has removed several existing functional features in Swift 3 to make the language more coherent (most famously, curry syntax, which also really fought against how Swift intends to works). The way you've defined f() is extremely un-Swift-like, both being a top-level function (which Swift avoids) and by not naming its parameters. Swift allows such things, but doesn't cater to them. The Swift team is anxious to hear examples where this syntax would be useful, and that map to common problems that Swift developers are likely to encounter (they've indicated they would explore re-adding the feature more fully if such examples were presented). See the evo-proposal for the thread to discuss this on if you have examples.

Broadly, tuples in Swift are weak sauce. They don't live up to being real "anonymous structs" the way you would expect, and are barely first-class types (you can't attach extensions to them, for instance). There are several proposals open to improve tuples, but in Swift 3, they aren't really a very powerful tool beyond very simple uses.

Swift create function closure with shortened dollar sign syntax support

Swift 2.0 already have this expression built in, but if you want to write it yourself, it's as simple as

extension CollectionType {    
func each(@noescape expression: (Self.Generator.Element, int) -> ()) {
for item in self {
expression(item)
}
}
}

And with the index:

extension CollectionType {    
func each(@noescape expression: (Self.Generator.Element, Int) -> ()) {
for (i, item) in self.enumerate() {
expression(item, i)
}
}
}

By extending CollectionType this works on both Array and other collections, such as Set.

Is there shorthand to specify a specific overload when passing a function as a closure-based argument?

In Swift, the "base name" of a method is the method name without any arguments:

  • Foo.init(x:y:z:)Foo.init
  • Foo.bar(_:)Foo.bar
  • Foo.baz(baz:)Foo.baz
  • Foo.quux()Foo.quux

When referring to a method by name (rather than calling it), Swift will allow you to refer to it by its base name, so long as the usage is not ambiguous:

struct Foo {
func f(intValue: Int) {}
}

let f = Foo().f

However, when there are multiple methods with the same base name, you can run into situations with ambiguity:

struct Foo {
func f(intValue: Int) {}
func f(stringValue: String) {}
}

let f = Foo().f // error: ambiguous use of 'f'

When this happens, you can either:

  1. Use explicit typing to help disambiguate the methods, if possible:

    let f1: (Int) -> Void = Foo().f
    let f2: (String) -> Void = Foo().f

    Or,

  2. Refer to the method by its fully-qualified name (name with parameter names included):

    let f1 = Foo().f(intValue:)
    let f2 = Foo().f(stringValue:)

In your case, because both methods have the same type signature, you can't use approach (1) to disambiguate between the calls, and will have to resort to (2).

The issue you ran into is that the parameter name you were using was slightly off:

// ❌ Foo.init(doubleValue:)
// ✅ Foo.init(valueToDouble:)

let foos = values.map(Foo.init(valueToDouble:)) // ✅

This does work, and will work as a shorthand instead of having to call the method directly inside of a closure argument.

A function with parameters that doesn't require its parameters at the call site?

TL:DR There is no "call site". You are referring to a function, not calling it.


Let's take a simpler case:

let array = [1,2,3]
func doubler(_ i:Int) -> Int { i*2 }
let array2 = array.map(doubler)
print(array2) // [2,4,6]

The goal is to use map to multiply every element of an array of Int by 2. And we did that.

Now, we could have done it like this:

let array2 = array.map {$0*2}

But what is that {$0*2}? It's shorthand for this:

let array2 = array.map { (i:Int) -> Int in
let i2 = i*2
return i2
}

In other words, the curly braces are the body of an anonymous function that takes an Int and returns an Int.

But no law says that the map parameter must be an anonymous function. It happens we already have a real function that takes an Int and returns an Int — doubler.

So instead of a function body in curly braces, we can pass the named function. How? By a function reference. We are not calling doubler, we are telling map the name of the function that it will call.

The simplest way to form a function reference is just to use the bare name of the function. It isn't the only way, but there's no problem using it here. (For more about function reference syntax, see my How do I resolve "ambiguous use of" compile error with Swift #selector syntax?)


Extra for experts: my favorite use of this shorthand is:

let sum = array.reduce(0, +)
print(sum) // 6

What just happened??? The same thing: in Swift, + is the name of a function.

What does $0 represent in closures in Swift?

It's a shorthand argument name.

From the Swift Book:

“Swift automatically provides shorthand argument names to inline
closures, which can be used to refer to the values of the closure’s
arguments by the names $0, $1, $2, and so on.”

— Apple Inc. “The Swift Programming Language.”

It helps reduce the verbosity of your code (sometimes at the cost of readability), so you don't have to write out long argument lists when defining closures.



Related Topics



Leave a reply



Submit