What Is a LazyMapCollection in Swift

What Is a LazyMapCollection in Swift

At first glance, the collection types defined by the Swift standard library look similar to the ones you find in other programming languages. But if you dig a little deeper, you start to see the differences.

You can ask a dictionary for its keys and values through its keys and values properties. Take a look at this example. We create a dictionary of type [String: Int] with three elements.

let dictionary = ["One": 1, "Two": 2, "Three": 3]

We can ask dictionary for its keys through its keys property. Quick question. What's the type of keys? If you guessed [String], then you guessed wrong.

let keys = dictionary.keys

The keys constant is of type LazyMapCollection<Dictionary<String, Int>, String>. What's that about? We need to dig into the Swift source code to find the answer.

What Is a LazyMapCollection in Swift

Swift Has the Answer

We can find out more about the keys property by pressing Command and clicking keys in the playground. This takes us to the interface of the Dictionary structure.

/// A collection containing just the keys of the dictionary.
/// When iterated over, keys appear in this collection in the same order as they
/// occur in the dictionary's key-value pairs. Each key in the keys
/// collection has a unique value.
///     let countryCodes = ["BR": "Brazil", "GH": "Ghana", "JP": "Japan"]
///     for k in countryCodes.keys {
///         print(k)
///     }
///     // Prints "BR"
///     // Prints "JP"
///     // Prints "GH"
public var keys: LazyMapCollection<[Key : Value], Key> { get }

While this gives us more information about the keys property, including a code example, it doesn't help us on our quest. Did you know that Apple open-sourced Swift? Seriously. That should help us find the answer to our question.

If you want to follow along, visit the repository on GitHub, clone it, and open it in a text editor, such as Atom, Sublime Text, or Visual Studio Code.

git clone https://github.com/apple/swift.git

The file we're interested in lives in the standard library folder, stdlib. Navigate to stdlib > public > core and look for a file named HashedCollections.swift.gyb. Don't worry about the gyb extension. That's a different story for another day.

Look for the keys property in this file. The implementation of this computed property looks like this.

public var keys: LazyMapCollection<Dictionary, Key> {
  return self.lazy.map { $0.key }

This brings us closer to the answer. The value of keys is the result of a map(_:) operation on the value of the collection's lazy property. That's a step in the right direction.

To continue our journey, we need to learn more about the lazy property. Return to your playground, press Command, and click the keys property. This takes you once more to the interface of the Dictionary structure.

Interface of Dictionary Structure in Swift

From the jump bar at the top, select the lazy property.

Interface of Dictionary Structure in Swift

This is what the interface of the lazy computed property looks like. The comments are particularly interesting.

/// A sequence containing the same elements as this sequence,
/// but on which some operations, such as `map` and `filter`, are
/// implemented lazily.
/// - SeeAlso: `LazySequenceProtocol`, `LazySequence`
public var lazy: LazySequence<Dictionary<Key, Value>> { get }

The value of lazy is the sequence itself. The only difference is that an operation is performed on the elements of the sequence. The lazy sequence is of type LazySequence<Dictionary<Key, Value>>.

We're almost there. Press Command once again and click LazySequence to bring up its interface. The first few lines are most interesting to us.

/// A sequence containing the same elements as a `Base` sequence, but
/// on which some operations such as `map` and `filter` are
/// implemented lazily.
/// - See also: `LazySequenceProtocol`
public struct LazySequence<Base : Sequence> : LazySequenceProtocol, _SequenceWrapper {


What's important to remember is that a LazySequence contains the elements of the sequence with an operation performed on each sequence. Notice that LazySequence conforms to the LazySequenceProtocol. Press Command and click LazySequenceProtocol for the final clue.

Scroll to the first lines of the comments that precede the LazySequenceProtocol definition. These comments tell us what's going on.

/// A sequence on which normally-eager operations such as `map` and
/// `filter` are implemented lazily.
/// Lazy sequences can be used to avoid needless storage allocation
/// and computation, because they use an underlying sequence for
/// storage and compute their elements on demand.  For example,
///     [1, 2, 3].lazy.map { $0 * 2 }
/// is a sequence containing { `2`, `4`, `6` }.  Each time an element
/// of the lazy sequence is accessed, an element of the underlying
/// array is accessed and transformed by the closure.
/// Sequence operations taking closure arguments, such as `map` and
/// `filter`, are normally eager: they use the closure immediately and
/// return a new array.  Using the `lazy` property gives the standard
/// library explicit permission to store the closure and the sequence
/// in the result, and defer computation until it is needed.


public protocol LazySequenceProtocol : Sequence {


The map(_:) operation is usually an eager operation. This means that the closure we pass to the map(_:) function is immediately performed on every element of the sequence. This isn't true for types conforming to the LazySequenceProtocol. To optimize performance and memory usage, the values of the keys and values properties of a dictionary are computed lazily.

This means that, when you ask a dictionary for its keys, it returns a LazyMapCollection object. The elements of this collection are computed on demand. This is only possible because the map(_:) operation is performed lazily instead of eagerly. Remember the implementation of the keys property of the Dictionary structure we explored earlier.

public var keys: LazyMapCollection<Dictionary, Key> {
  return self.lazy.map { $0.key }

The closure passed to the map(_:) function is stored by the standard library. This is important since the closure is not immediately executed. It's executed whenever an element of the keys collection is accessed.

Exploring the Swift Standard Library

Exploring the source code of the Swift standard library can take you down a rabbit hole, but I hope you agree that it teaches you quite a bit along the way. I really enjoy digging through the source code of open source projects and exploring the Swift project is no different.

If you want to become a better developer, then such journeys are great opportunities to learn and extend your boundaries as a developer. It's fine if you don't understand every detail. The goal is to leave your comfort zone and learn as you go.