[swift-evolution] Remove forEach?

Erica Sadun erica at ericasadun.com
Tue Dec 8 15:56:31 CST 2015


I'd prefer retain `for-each`. I like it as a natural termination step for functional chaining. Here's what I wrote about the topic when it first appeared. There are bits of the discussion that I'd refine since I first wrote this but I decided it was easier to paste this as a whole and save my afternoon.

-- E

ForEach: Just when I finally got around to implementing this generically:

public extension SequenceType {
    func mapDo(p: (Self.Generator.Element) -> Void) {
        for x in self {p(x)}
    }
}
Apple introduced forEach.

Array(1...5).map({$0 * 2}).forEach{print($0)}
You use this procedurally when you don’t collect/return the results. Notice how I’ve followed the Rule-of-Kevin-Ballard <http://twitter.com/Eridius> above. No parens around the braces because the closure is procedural.

public func forEach(@noescape body: (Self.Generator.Element) -> ())
This new language feature eliminates the awkward “for _ in” construct and provides a procedural end-point for functional chains. If you want to continue the chain (for example if you want to throw a print($0) in the middle) continue using map, which offers pass-through.

Apple’s pointers:

Unlike for-in loops, you can’t use break or continue to exit the current call of the body closure or skip subsequent calls. 
Also unlike for-in loops, using return in the body closure will only exit from the current call to the closure, not any outer scope, and won’t skip subsequent calls. 
Due to these limitations, the forEach member is only recommended when applied to a chained series of functional algorithms (e.g. foo.map {…}.filter {… }.forEach { …}) and when the body is small. In other cases, we recommend using the for..in statement. (18231840)


> On Dec 8, 2015, at 1:28 PM, Kevin Kachikian via swift-evolution <swift-evolution at swift.org> wrote:
> 
> I support keeping forEach in. I use it enough myself. It also makes it easier for beginning Swift programmers to understand syntactically.
> 
> Kevin
> 
> 
>> On Dec 8, 2015, at 12:21 PM, Sean Heber via swift-evolution <swift-evolution at swift.org> wrote:
>> 
>> It’d probably be confusing without a new keyword or symbol, but in the case of @noescape closures, maybe “return" could return from the outer scope instead of the inner one. Smalltalk has a distinction kind of like this.
>> 
>> In some ways, I think “return” should be reserved for actually returning from a *function* and “^” or something could exist for returning from a *closure* and that distinction would eliminate strange nested returns that sometimes crop up in these scenarios.
>> 
>> So as written, your example would just do the “right thing” if “return” was so defined. If you wanted to explicitly do the wrong thing, it could be written as:
>> 
>>   func indexOf_foreach(element: Element) -> Int? {
>>       self.indices.filter { idx in self[idx] == element }.forEach { idx in
>>           ^ idx
>>       }
>>       return nil
>>   }
>> 
>> If you tried to use “return” in an escaping closure, then it could be an error.
>> 
>> l8r
>> Sean
>> 
>> 
>>> On Dec 8, 2015, at 1:07 PM, Chris Eidhof via swift-evolution <swift-evolution at swift.org> wrote:
>>> 
>>> Hi all,
>>> 
>>> As long as the `for` loop is in the language, I don’t really see the use of `forEach`. I understand that it can read much nicer to write something like `a.map { something }.filter { somethingElse }.forEach {` rather than a `for` loop. However, I think the costs don’t outweigh the benefits. It might look like a for loop can just be replaced by a `forEach`, however, this is not true in the general case. For example, consider the following example:
>>> 
>>>   func indexOf_foreach(element: Element) -> Int? {
>>>       self.indices.filter { idx in self[idx] == element }.forEach { idx in
>>>           return idx
>>>       }
>>>       return nil
>>>   }
>>> 
>>> The code above (I realise it’s quite inefficient) might look like it’s returning the first index for which the filter’s condition returned true. However, the first occurrence of return is actually returning inside the closure, not the outer function. So the result of this function is always nil.
>>> 
>>> Another solution would be to give a good warning/error message here (you’re trying to return an Optional value where we expect a ()). However, this is also problematic when dealing with side-effects. For example:
>>> 
>>> [1,2,3,4,5].forEach { num in
>>>  print(num)
>>>  if num > 3 { return }
>>> }
>>> 
>>> I think it’s quite easy to get things wrong with forEach, so I’d propose removing it and rather having a regular for loop. (Erica-style).
>>> 
>>> Chris
>>> 
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151208/e3e060c3/attachment.html>


More information about the swift-evolution mailing list