Home > Software design >  Map with parentheses implicitly inserting item to init
Map with parentheses implicitly inserting item to init

Time:10-12

I happen to see some code with map with parentheses seems implicitly passing item to init. I'm confused how it is working properly. Would anyone explain me about this grammar or cite any doc for me?

class User: NSObject {
    let name: String
    
    init(name: String) {
        self.name = name
    }
}

let names: [String] = ["John", "Bob", "Max"]

let users: [User] = names.map( // parentheses instead of curly brackets
    User.init // is name passed to init implicitly?
)

users.forEach {
    print($0.name) // name is inserted properly
}

CodePudding user response:

If you option-click on map in Xcode, you will see that it is a function with this declaration:

func map<T>(_ transform: (Self.Element) throws -> T) rethrows -> [T]

map takes one parameter which is closure (or function) that takes an element of the sequence it is called on and returns a value of the same or another type T. map calls that closure/function for each element and builds an Array of that type T (aka [T]).

User.init is a function which is of the type (String) -> User. It takes a String and transforms it into a User.

So calling:

let users: [User] = names.map(User.init)

has the same effect as calling:

let users: [User] = names.map { User(name: $0) }

which is using trailing closure syntax to pass a closure of type (String) -> User to map().


A way to get from here to there

Make the following assignments:

let x = User.init  // this is shorthand for User.init(name:)
let y = { User(name: $0) }

and option-click on x and y. In both cases, they represent a closure of type (String) -> User and both do the same thing.

You can call x("Bob") and y("Bob") to create a User with name "Bob".

If you start with the form you are most used to seeing:

let users: [User] = names.map { User(name: $0) }

and remove the trailing closure syntax:

let users: [User] = names.map({ User(name: $0) })

and then substitute the equivalent closure, you arrive at the other style of map:

let users: [User] = name.map(User.init)

Note: Swift allows the shorthand notation User.init because there is only one initializer for User. If User had a second initializer that took an Int for example, then you'd have to specify the parameter name to remove the ambiguity when assigning to the variable x, but not when using it with map because Swift is smart and knows that you called map on [String], so the transform function must be one that takes a String.

  • Related