Yes, it would be nice to lift those language limitations, but those are hardly forEach
's fault.
This is why I would like to create a way for return
, break
, and continue
to work in closures so that functions like forEach
can behave and look more like for
loops.
I've met this feature in Ruby, and I loved it (and I guess it's initially a Smalltalk thing). In Ruby, we distinguish procs from lambdas. Procs affect control flow one-level up. Lambdas can not.
Ruby procs & lambdas
# prints
# lambda 1: foo
# lambda 2: foo
# proc 1: bar
# f: bar
def f
l1 = lambda do 'foo' end
puts "lambda 1: #{l1[]}"
l2 = lambda do return 'foo' end
puts "lambda 2: #{l2[]}"
p1 = proc do 'bar' end
puts "proc 1: #{p1[]}"
p2 = proc do return 'bar' end
puts "proc 2: #{p2[]}"
puts "end"
end
puts "f: #{f()}"
There are other funny Ruby features, such as breaking with a value:
def f
[1, 2, 3].each do |x|
break x * 2 if x == 2
end
end
f # 4
Now of course, changing Swift's forEach
behavior would be a breaking change:
func f() {
[1, 2, 3].forEach { x in
guard x != 2 else {
// Returning from f would be a rubyism, and a breaking change
return
}
print(x)
}
}
Why else would you want this features? A popular request is for SwiftUI’s ForEach, but that could be addressed with a lazy for-loop build method in result builders.
forEach
helps to avoid declaring a variable where it's not needed. Does not have to be a long chain, can be take this, filter, do that:
let items = [...]
let filteredItems = items.map { ... }.filter { ... }
for item in filteredItems {
// ...
}
vs.
let items = [...]
items.map { ... }
.filter { ... }
.forEach { item in
// ...
}
In fairness, you can still do it without an extra variable:
let items = [...]
for item in (items.map { ... }.filter { ... }) {
// ...
}
although it is neither nice nor scaleable:
items
.map { ... }
.filter { ... }
.sorted { ... }
.compactMap { ... }
.something { ... }
.somethingElse { ... }
.and { ... }
.so { ... }
.on { ... }
.and { ... }
.so { ... }
.forth { ... }
.forEach { .... }
vs
for item in (items.map { ... }.filter { ... }.sorted { ... }
.compactMap { ... }.something { ... }.somethingElse { ... }
.and { ... }.so { ... }.on { ... }.and { ... }
.so { ... }.forth { ... }) {
// ...
}
perhaps formatted like so to make it ok-ish?
for item in (
items
.map { ... }
.filter { ... }
.sorted { ... }
.compactMap { ... }
.something { ... }
.somethingElse { ... }
.and { ... }
.so { ... }
.on { ... }
.and { ... }
.so { ... }
.forth { ... }
) { ... }
This formatting is not acceptable to me within the for in
body. I also tend to avoid the same with guard
statements, for example:
Ugly:
guard let item = items.first(where: { item
// ...
}) else {
// ...
return
}
Good:
let item = items.first {
// ...
}
guard let item = item else {
// ...
return
}
I use both extensively in my code, depending on context, to express emphasis and focus the readers attention: forEach
to emphasize the collection as a whole, for in ...
to focus attention on what is happening to each element.
That formatting is not nice, I agree. BTW, this is one of the reasons I'm using a custom postfix not
operator:
let isNotSomething = items
.map { ... }
.filter { ... }
.sorted { ... }
.compactMap { ... }
.something { ... }
.not
vs
let isNotSomething = !(items
.map { ... }
.filter { ... }
.sorted { ... }
.compactMap { ... }
.something { ... })
One thing I don't see mentioned here is the fact that forEach
is also very convenient to use when you have optional collections. Is the collection nil
? Then simply don't execute the block.
When using for ... in ...
, you have to have unwrap your collection first ¯\_(ツ)_/¯
What then about for item in sequence? { … }
? Would it be a good idea to have that?
…But I certainly like forEach at the end of a possibly long chain.
And forEach can fetch the type of the items of e.g. Sequence<Int>
, the for loop currently cannot do that.
This is probably not doing what you think it does. And, it could be accomplished with a for in
loop.