Do we want `forEach`?

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.

1 Like

Yes, it would be nice to lift those language limitations, but those are hardly forEach's fault.

1 Like

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)
  }
}
2 Likes

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 { ... }
) { ... }

1 Like

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.

1 Like

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 { ... })
1 Like

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 ¯\_(ツ)_/¯

3 Likes

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.