-
Notifications
You must be signed in to change notification settings - Fork 334
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add rule to prefer for loops over functional forEach { ... }
method
#234
Conversation
Adding a link to a comment about |
Thanks for sharing this. It seems like our style guide is aligned with Ben's perspective, which is validating IMHO. |
@@ -1542,6 +1542,8 @@ _You can enable the following settings in Xcode by running [this script](resourc | |||
} | |||
``` | |||
|
|||
</details> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIce catch.
README.md
Outdated
// ALSO FINE, since forEach is useful when paired with other functional methods in a chain. | ||
planets | ||
.filter { !$0.isGasGiant } | ||
.map { PlanetTerraformer(planet: $0) } | ||
.forEach { $0.terraform() } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Asking to learn (and I apologize for the lazy Q, as I know I could try this out...): at what point does the SwiftFormat rule implementation allow forEach(…)
? Is it once the expression is multiple lines? I'd love if you want to link me to the code that makes this decision, if that's straightforward to do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By default the SwiftFormat rule avoids applying the rule in two cases:
- If the subject of the
forEach
call includes line breaks (handled here with an explicit check). For example, it would be weird to convert:
planets
.filter({ !$0.isGasGiant })
.map({ PlanetTerraformer($0) })
.forEach({ $0.terraform() })
to a for loop formatted something funny-looking like
for planet in planets
.filter({ !$0.isGasGiant })
.map({ PlanetTerraformer($0) })
{
print(planet)
}
- If the subject of the
forEach
call uses trailing closure syntax, likeplanets.filter { !$0.isGasGiant }.forEach { print($0) }
(handled here by virtue of}
not being handled in one of the switch cases -- should realistically be called out more explicitly).
We exclude this case because converting this to a for loop causes a new warning to be emitted:
// warning: trailing closure in this context is confusable with the body of the statement; pass as a parenthesized argument to silence this warning
for planet in planets.filter { !$0.isGasGiant } {
print(planet)
}
A typical use case of chained functional operations hits both of these rules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the subject of the forEach call uses trailing closure syntax, like planets.filter { !$0.isGasGiant }.forEach { print($0) } (handled here by virtue of } not being handled in one of the switch cases -- should realistically be called out more explicitly).
I improved how this is handled in the code here: nicklockwood/SwiftFormat@4d92ba2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for explaining @calda and for that follow up!
@@ -1562,6 +1564,42 @@ _You can enable the following settings in Xcode by running [this script](resourc | |||
planets.first { $0.isGasGiant } | |||
``` | |||
|
|||
</details> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Co-authored-by: Michael Bachand <[email protected]>
I would prefer to give the developer the option of choosing the best tool for the job. For example, occasionally I group things in logical chunks, and one-liners can sometimes help with organization:
|
@swiftal64, perhaps for loops can still be used in this way: for star in stars { star.orbit() }
for planet in planets { planet.orbit() }
for moon in moons { moon.orbit() } This sort of usage would still be consistent with the style guide. |
Summary
This PR proposes a new rule to prefer using for loops over the functional
forEach { ... }
method, unless usingforEach
as the last element in a functional chain.Autocorrect is implemented in the new
forLoop
SwiftFormat rule, which was added in nicklockwood/SwiftFormat#1490.Reasoning
For loops are more idiomatic than the
forEach
method, and are typically familiar to all developers who have experience with C-family languages.For loops are also more expressive than the
forEach
method. For loops support thereturn
,continue
, andbreak
control flow keywords, whileforEach
only supportsreturn
(which has the same behavior ascontinue
in a for loop).Please react with 👍/👎 if you agree or disagree with this proposal.