Add rule: prefer lazy.map before single-pass operations - #402
Merged
Conversation
Enables SwiftFormat's `preferLazyMap` rule and documents the corresponding style-guide entry (a sibling of the existing `preferMinOverSorted`, `preferFirstWhere`, and `preferContains` collection rules). Also bumps the SwiftFormat artifactbundle to the `2026-08-20-b` nightly, the first build that includes the rule along with its follow-up correctness fixes. `map` allocates an array of every transformed element. When all that happens to that array is a single walk over it, the allocation is waste: `lazy.map` transforms each element as it is visited. The qualifying operations consume their receiver exactly once — `min`, `max`, `reduce`, `joined(separator:)` — plus `contains`, `allSatisfy`, and `first(where:)`, which can stop early and skip transforming the rest. Enabling the rule autocorrected nothing in this repo; its six Swift files have no eligible sites.
calda
reviewed
Aug 20, 2026
`contains` shows the larger of the two wins, so it goes first: the eager version transforms every element before looking at any of them, even when the answer is the first one. Keeping a `min` example alongside it covers the plain allocation-avoidance case, and dropping the third example leaves the block easier to read. The list of qualifying operations moves below the examples, where it reads as a reference rather than a preamble.
The headline is the only part of the entry that reaches the AI skill, so it has to carry enough to act on. Spells out `joined(separator:)` rather than `joined` for that reason: bare `joined()` is deliberately not rewritten, because on a lazy sequence it resolves to the sequence-flattening overload and silently turns a `String` into a lazy sequence. A headline saying just `joined` would be read as license to rewrite it.
calda
reviewed
Aug 20, 2026
calda
reviewed
Aug 20, 2026
calda
reviewed
Aug 20, 2026
calda
reviewed
Aug 20, 2026
|
|
||
| This applies to any operation that turns the sequence into a single value: `joined(separator:)`, `min`, `max`, `reduce`, and also `contains`, `allSatisfy`, and `first(where:)`, which stop as soon as they have an answer. It does not apply to an operation that produces another sequence, like `filter` or `sorted()`, since the transformed elements are needed more than once — or, in `sorted()`'s case, have to be materialized anyway. | ||
|
|
||
| When the operation takes a predicate, folding the transform into that predicate is better still, because then there is no `map` to make lazy: |
jparise
force-pushed
the
prefer-lazy-map
branch
from
August 21, 2026 15:47
a5ad8c0 to
0be7509
Compare
The `contains` example was a poor one to lead with: it is better written
`rows.contains(where: { $0.title.isEmpty })`, with no `map` at all. That holds for
every short-circuiting operation, since `xs.map(f).contains(where: p)` always
collapses to `xs.contains { p(f($0)) }` — so those make weak exemplars, because
adding `.lazy` is not the best available fix.
Leads with `joined(separator:)` and `min` instead, the two cases where there is no
better form: `joined` needs a sequence of strings, and the alternative to
`map { $0.y }.min()` is `min { $0.y < $1.y }?.y`, which is the shape rejected
during review of the rule itself.
Keeps the short-circuiting operations in the list, since the rule does improve
those chains, but adds a second example showing that folding the transform into
the predicate beats making the map lazy.
Also rewords the headline: "consumed in a single pass" was doing too much work.
"Reduces to a single result" says the same thing in plainer terms and explains
why `filter` is excluded — it produces another sequence rather than a value.
jparise
force-pushed
the
prefer-lazy-map
branch
from
August 21, 2026 18:17
0be7509 to
e040daa
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Enables SwiftFormat's
preferLazyMaprule and documents the corresponding style-guide entry (a sibling of the existingpreferMinOverSorted,preferFirstWhere, andpreferContainscollection rules).Reasoning
mapallocates an array of every transformed element. When all that happens to that array is a single walk over it, that allocation is waste —lazy.maptransforms each element as it's visited instead.Qualifying operations consume their receiver exactly once (
min,max,reduce,joined(separator:)) or can additionally stop early and skip transforming the rest (contains,allSatisfy,first(where:)). The style-guide entry notes the converse too:lazyis only right when the result really is consumed once, sincesorted()materializes anyway and anything walking the result twice redoes the transform.