Group Group Group Group Group Group Group Group Group

Programming in Swift · Closures and Collections | Ray Wenderlich


#1

This is a companion discussion topic for the original entry at https://www.raywenderlich.com/5994-programming-in-swift/lessons/35

#2

Why wouldn’t you use

names.sort(by: >)

instead of

names.sort(by: {(a, b) -> Bool in
a > b
})


#3

You definitely could use > to replace the long form closure, just like we used + in our example of reduce later in this same video!

One of our goals in this video was to show the many syntactical options of closures. None of them are technically more correct than the others, but if you can use a function that already exists, like >, that’s probably a better idea than rewriting it :]


#4

compactMap:

25%20PM

You say that the compactMap function takes a closure that takes one element and returns an optional value.

Is it really an optional value if we know for sure that it’s a non-nil value? If it was an optional, wouldn’t we have to unwrap it first before using it or making changes to it? (Like Int(input) is transforming the possible Ints in the closure)?

flatMap:

34%20PM

You say that the flatMap function takes a parameter (consisting of a closure) and returns an array.

In turn, the closure takes one parameter and returns the same type.

Can you please explain what you mean by that?

The closure declaration here is array -> [String]. Is ‘array’ here returning something?


#5

Yes it is. The closure will return nil for the nil elements. CompactMap will then not include those nils in the array that it returns. We know that the return from compactMap is non-nil values. The closure itself has to return something for every element of the input, so it needs to return nil for the nil ones.

Here is an example, using the same closure for both map and compactMap. The closure can return nil; map will include the nils in the returned array; compactMap will not include the nils.

let userInputs = ["1", "2", "A", "3"]
let mapped = userInputs.map {input in Int(input)}
// [1, 2, nil, 3]   (mapped type is [Int?])
let compact = userInputs.compactMap {input in Int(input)}
// [1, 2, 3]   (compact type is [Int])

#6

Thanks for the clarification!

For both methods in my original comment, is it unnecessary to include the closure’s parameters/return type because they are inferred given the method?

What are the types of ‘input’ and ‘array’ that have been omitted from the closures?


#7

For the map methods, the type of the parameter will always be the type of the array elements being mapped.

For the first one, userInput is an array of String, so the parameter of the compactMap closure is a String, named input.

For the second one, arrayOfDwarfArrays is an array of arrays of String, so the parameter of the flatMap closure will be an array of String, named array.
Inside that closure, since array is an array of String, the parameter for the filter closure will be a String, named dwarf.

The second one does not actually need to define the return type of the closures, as that can be inferred from the content of the closures. It is sometimes a good idea to declare them anyway, because then the compiler can tell you if your code is returning what you think it is returning.

For the first one, you could put in the type of the return. If you try to make it Int, it will show an error. If you then make it Int?, it will accept it, thus letting you know that it is in fact an optional.

//let validInput = userInput.compactMap {input -> Int in Int(input)} // compile error
let validInput = userInput.compactMap {input -> Int? in Int(input)}

#8

I suppose my confusion stems from the fact that the code still works with the return type being omitted.

Does it infer that we’re returning an array of Ints because the values are being transformed into Int values in the body of the closure? Or is there something more to it?

.sorted, for instance, always compares two elements and returns a Bool. Are the map methods similar where they have that transformation element as an underlying type?


#9

That’s it, nothing more to it.

They don’t have any particular type, it depends on what you put there for the transformation. You could return Doubles instead of Ints, for instance. Or a new String with an “*” added on the end.

Swift likes to infer type. It will determine what the type actually is anyway, and inform you with an error if it doesn’t match what you declared.

Closures in particular have been made as simple as possible. Notice that you don’t have to write “return Int(input)”, though you can. It takes it as implied that you want the expression result to be returned, because duh.


#10

I really appreciate your help with this.

To sum up:

/For the map methods, the type of parameter will always be the type of the array of elements being mapped./

The .map closure takes as a parameter the values in the sequence and returns an alternative mapped value for each item (in a new array). Unless otherwise specified/inferred, the returned array will be of the same type as the initial collection.

The .compactMap closure takes as a parameter the values in the sequence and returns an optional value for each. The method then returns an array of the non-nil values in a new array.

The .flatMap closure takes as a parameter the values in the sequence (of all elements) and returns a single array. You use this method when the transformation produces a collection for each element-- for example if you have two arrays, they will be returned combined in a single array.

They don’t have any particular type, it depends on what you put there for the transformation. You could return Doubles instead of Ints, for instance. Or a new String with an “*” added on the end.

Does this also work with .flatMap? Or does this one in particular have to return the same type as the parameter?

I tried transforming Ints into Doubles, and Strings into Ints with .flatMap, but both times I got an error message saying that it didn’t match the parameter type.


#11

It will always be specified or inferred. The output type of the map methods is determined by the type returned by the closure, not the type of the input sequence.

It should. Here is an example, with an array of Int as input. The closure produces an array of Doubles. With map, you end up with an array of arrays of Doubles. With flatMap, and the same closure, you get a single array of Doubles.

let intArray = [1, 2, 3]
let mapArray = intArray.map {item in [Double(item), Double(item) / 2]}
print(mapArray)

[[1.0, 0.5], [2.0, 1.0], [3.0, 1.5]]

let flatMapArray = intArray.flatMap {item in [Double(item), Double(item) / 2]}
print(flatMapArray)

[1.0, 0.5, 2.0, 1.0, 3.0, 1.5]