Question re .map and closures

I'm reading a book with tutorials in Swift and in it, they list the following code:

class Person {
var firstName: String
var lastName: String
}

Based on that class definition, it then lists the following code which I do not understand:

var imposters = (0...100).map { _ in
Person(firstName: "John", lastName: "Appleseed")
}

As per the last block of code, is it creating a numeric range of 0 to 100, and within that numeric range, creating an object with the properties firstName and lastName, thus creating one hundred objects in a variable called "imposters"? I just want to make sure that I'm understanding it correctly, as 1) I'm not that familiar with the .map method and 2) I thought a closure had to have actual behavioral code like a function. :frowning:

Also, how do I interpret or read the keyword in in the enclosure so that it makes sense to me when I read the code? What does "in" represent in a closure? Thank you.

So the map method for a sequence of elements of type T takes a function/closure that transforms a value of type T to a value of type U (note: U can be the same type as T, but it doesn't have to be). It then applies this function to each of the elements of the sequence and generates a sequence of elements of type U. In this case T is Int and U is Person.

It does. And here the behavior is to take an Int value, ignore it, and to initialize and return an instance of Person with the first and last names "John" and "Appleseed" respectively.

Preceding the in are the parameters of the transforming function. So if you see {a, b, c in … } you can read it as "Using the parameters a, b, and c as inputs in the following function. However, you can use _ to more or less say "I know there's a value that's going to be passed in here, but I don't care what it is, so I'm not going to bother assigning it to a variable with a name." And that is what is happening in that example. You know you are getting an Int from that range, but you don't need it to create your Person, and you aren't doing anything else with it, so you just ignore it using _.

1 Like

So, one way that I've thought about this is that the closure is a function that you make on the spot. If you wanted to use map to modify the numbers in the 0 to 100 range, you could write out a function:

func makeImposter(number: Int) -> Person {
  return Person(firstName: "John", lastName: "Appleseed")
}

You might notice that we didn't use the number parameter at all in our function.

If I were to write the same function as a closure, I could write:

let makeImposter: (Int) -> Person = { number in 
  Person(firstName: "John", lastName: "Appleseed")
}

This would work mostly identically to the function, since you're just defining a new variable that is assigned a closure.

Here, the in means that it is one of the inputs to your closure. It performs the same function as the number parameter part of the func makeImposter(number: Int) -> Person. Since a closure doesn't have a definition ahead of time, it needs a way to separate a named parameter input from other variables.

Going a bit further, this version of your map never uses the Int that you are running the closure for each time. In swift, when we don't use a variable, it's generally a warning, so we can ignore a closure input that we don't use by putting _ to specify that we don't care what the value is because it is not being used.

So, to summarize, you have a range of numbers 0 - 100. For each of those numbers you're going to start with a number and turn it into something else by mapping it from the number to a value. The change that you make is defined by the closure { _ in ... } which takes a number (we're ignoring it) and returns a Person.

1 Like

Thank you (both) for these detailed explanations. I'd like to ask though, what is the purpoe then of iterating with .map and the closure? I tried printing the results using the below code:

print(imposters)

But instead of seeing the results of the function and iteration, I see the following in my results panel:

"[__lldb_expr_48.Person"

That code appears about a hundred times. However, I am able to see the results using the index and dot notation.

I suppose I'm confused then as to why the names do not appear as contained in the variable when I use the print command.

If you want the type to use a custom description, then conform it to CustomStringConvertible.

I'm not sure there is actually a great use for this chunk of code:

var imposters = (0...100).map { _ in
  Person(firstName: "John", lastName: "Appleseed")
}

This will always create an array with 101 Person objects that are all named John Appleseed. Map is really useful when you need need to take a list of one type of thing and convert it into a list of another type of thing.

Here's an example of what you could do with map, you can plop this in a playground, and it should work:

import Foundation

struct File {
   let name: String
   let type: String
}

// Some files that you already have
let files = [
  File(name: "image", type: "png"),
  File(name: "secret plans", type: "txt")
]

print(files)  // [__lldb_expr_9.File(name: "image", type: "png"), __lldb_expr_9.File(name: "secret plans", type: "txt")]

// Try uncommenting me to see what this does to the print
//
//extension File: CustomStringConvertible {
//    var description: String {
//        return "File: name=\(name), type=\(type)"
//    }
//}
//
//print(files) //[File: name=image, type=png, File: name=secret plans, type=txt]

guard let baseURL = URL(string: "www.example.com") else {
    fatalError("Invalid base url was formed")
}

let fileNames = files.map { file in "\(file.name).\(file.type)" }
// fileNames has been mapped from [File] to [String]
print(fileNames) //["image.png", "secret plans.txt"]

// This is similar to the above, we've mapped [String] to [URL]
// but we're also using an implicit variable name for the closure, instead of defining it
let serverURLs = fileNames.map { baseURL.appendingPathComponent($0) }
print(serverURLs) // [www.example.com/image.png, www.example.com/secret%20plans.txt]