If we’re having a religious argument, I’m strongly on the side of .forEach. One person’s “this is ugly code” is another person’s “this is much more readable code.” I like the constraints of .forEach, and knowing that it’s going to process everything and there’s not extra BS happening (compared to a general-use for() loop).
If I glance at a .forEach there’s a bunch of stuff I just instantly know about the code, which I think is super-valuable.
Except this isn’t really true. return in a forEach is a for-loop’s continue, and functions that throw exit early without processing all elements, like a break.
Fair. But I still think as a capper for a chain of transforms, .forEach can’t be beat. I personally would rather have the UNIX pipe metaphor over a BASIC ‘assign a variable and use it later’ metaphor.
It's more than a less clearly named for in ... loop. It's useful and flowy at the end of a long chain of higher-order functions such as filter, map, ..., as Wil described. I believe it's actually more clear than assigning the result to a throwaway variable if all you're doing is iterating over the result.
If anything, I'd be in favor of giving .forEach more power by having it return its input sequence as a discardable result, so .forEach could be used in the middle of such a transformation chain as well.
I meant the specific case in the post I was replying to… but I see Discourse hid the reply relationship because they were adjacent. Unhelpful of it in this case, ah well.
I'm not sure what you're asking here. Why is wrapping in a Task relevant here? I would expect this forEach to be equally applicable whether wrapped in a Task or not, but I would think most use would eventually (in the medium term) be in naturally async methods.
Personally, I think we should have forEach. Not just for parity with Sequence, but because it lends itself well to one of the fundamental ways of using Swift, chaining. You can make the same arguments used against forEach against pretty much the entire rest of Swift's collection APIs. Doesn't make them bad, just means users need to know you use them differently than raw loops. So far that doesn't seem to have been an issue. That a tool isn't useful in 100% of cases isn't a reason not to have the tool.
In fact, I'd go farther. I'd say forEach's signature should change to forEach(...) -> Self, so that later operations can be chained. This is a nice increase in utility for a very small change.
The reasoning was more so to understand how folks intend or see its use. It was a pattern that I have been seeing emerge when using some of these things in app contexts and I was wondering if some of the use cases folks were seeing were similar to that of .sink (but perhaps a better name).
How would that even work? AsyncSequence is not double-pass capable. So unless it is an escaping "diagnostic window for side effects" (which I have much stronger objections that Ben's objection to .forEach on... mainly because it makes thread safety and self consistency virtually impossible because it breaks the monad's encapsulation), im not sure I follow on how that could be achieved.
forEach was useful with UIKit due to the abundance of classes, but it will continue on the trip towards complete worthlessness unless two things are fixed:
Cannot reference 'mutating' method as function value.
I.e. it's only good when you already have an existing named closure to use with it.
Ah, I see what you're getting at. Yeah, I've been using Task's to start stream observer callbacks, but I've just been using raw loops since there is no forEach. Doesn't seem like there's any other option yet. forEach makes that slightly nicer for more complex chains.
You're right, I didn't think that through for the async case. In that case it seems easy to just return a custom type like all of the other async operators, one that just passes elements through.
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.
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.