Remove forEach?


(Chris Eidhof) #1

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


Add `filter` and `ifSome(then:)` to Optional
(Sean Heber) #2

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@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Brent Royal-Gordon) #3

As long as the `for` loop is in the language, I don’t really see the use of `forEach`.

Here’s a typical use of forEach for me:

  generateABunchOfSubviews().forEach(addSubview)

Seriously. I have one unreleased app which generates an entire software keyboard like that.

You could write it like:

  for subview in generateABunchOfSubviews() {
    addSubview(subview)
  }

But why use the extra lines and temporary variable when you don’t have to?

···

--
Brent Royal-Gordon
Architechies


(André Videla) #4

Hi,

As I understand it, the argument for remove forEach is that it’s easy to make confusing code that doesn’t do what it looks like it should do.

In this particular example:

  func indexOf_foreach(element: Element) -> Int? {
        self.indices.filter { idx in self[idx] == element }.forEach { idx in
            return idx
        }
        return nil
    }

I think the compiler should give us two warnings:
- closure returns type Int when it should return type ()
- function always return nil (which is a unit type just like () ) when it expects Option<Int>

In my opinion those warnings should be enough to make the programmer aware that this code doesn’t do what it looks like it does. Typically, returning a type when () is expected shouldn’t go silently.

As such I think keeping forEach is important as it allows for every elegant expressions and convey already the already established convention when you use forEach that “I”m only interested in side-effects with elements in this collection”

My proposition is then that instead of removing forEach, we should have better type check and warn when the cardinality of the expected type and the returned type don’t match.

-André

···

On 08 Dec 2015, at 20:07, Chris Eidhof via swift-evolution <swift-evolution@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Kevin Kachikian) #5

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@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@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Erica Sadun) #6

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@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@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@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Tyler Cloutier) #7

Why does that function even compile? For example, the following produces the compiler error: Unexpected non-void return value in void function

[1,2,3,4,5].forEach { x in
    let y = 0
    return x
}

Which is what I would expect. But this compiles just fine:

[1,2,3,4,5].forEach { x in
    return x
}

Is this just a compiler bug, or am I missing something?

Tyler

···

On Dec 8, 2015, at 2:18 PM, André Videla via swift-evolution <swift-evolution@swift.org> wrote:

Hi,

As I understand it, the argument for remove forEach is that it’s easy to make confusing code that doesn’t do what it looks like it should do.

In this particular example:

  func indexOf_foreach(element: Element) -> Int? {
        self.indices.filter { idx in self[idx] == element }.forEach { idx in
            return idx
        }
        return nil
    }

I think the compiler should give us two warnings:
- closure returns type Int when it should return type ()
- function always return nil (which is a unit type just like () ) when it expects Option<Int>

In my opinion those warnings should be enough to make the programmer aware that this code doesn’t do what it looks like it does. Typically, returning a type when () is expected shouldn’t go silently.

As such I think keeping forEach is important as it allows for every elegant expressions and convey already the already established convention when you use forEach that “I”m only interested in side-effects with elements in this collection”

My proposition is then that instead of removing forEach, we should have better type check and warn when the cardinality of the expected type and the returned type don’t match.

-André

On 08 Dec 2015, at 20:07, Chris Eidhof via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@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@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jordan Rose) #8

This falls out of the rule that single-expression closures have their types inferred, and that that inference includes coercion to a Void return type. That's because of code like this:

dispatch_sync(queue) {
  doSomeWork() // returns Bool, but I'm ignoring the result
}

Normally, the closure being passed to dispatch_sync would have an inferred type of '() -> Bool', but dispatch_sync expects a dispatch_block_t, which is '() -> Void'. You can "fix" this by explicitly ignoring the result ("_ = doSomeWork()"), but that's a little silly, so we added a rule that it's okay to ignore the result if the closure's supposed to return Void. That rule really shouldn't apply when the user explicitly wrote a return, though.

Jordan

···

On Dec 8, 2015, at 15:55, Tyler Cloutier via swift-evolution <swift-evolution@swift.org> wrote:

Why does that function even compile? For example, the following produces the compiler error: Unexpected non-void return value in void function

[1,2,3,4,5].forEach { x in
    let y = 0
    return x
}

Which is what I would expect. But this compiles just fine:

[1,2,3,4,5].forEach { x in
    return x
}

Is this just a compiler bug, or am I missing something?

Tyler

On Dec 8, 2015, at 2:18 PM, André Videla via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi,

As I understand it, the argument for remove forEach is that it’s easy to make confusing code that doesn’t do what it looks like it should do.

In this particular example:

  func indexOf_foreach(element: Element) -> Int? {
        self.indices.filter { idx in self[idx] == element }.forEach { idx in
            return idx
        }
        return nil
    }

I think the compiler should give us two warnings:
- closure returns type Int when it should return type ()
- function always return nil (which is a unit type just like () ) when it expects Option<Int>

In my opinion those warnings should be enough to make the programmer aware that this code doesn’t do what it looks like it does. Typically, returning a type when () is expected shouldn’t go silently.

As such I think keeping forEach is important as it allows for every elegant expressions and convey already the already established convention when you use forEach that “I”m only interested in side-effects with elements in this collection”

My proposition is then that instead of removing forEach, we should have better type check and warn when the cardinality of the expected type and the returned type don’t match.

-André

On 08 Dec 2015, at 20:07, Chris Eidhof via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@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@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Chris Eidhof) #9

I think having a different syntax for returning from an anonymous function would be a bad idea. They’re really the same thing.

···

On 08 Dec 2015, at 15:21, Sean Heber <sean@fifthace.com> 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@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Nate Cook) #10

The Swift compiler started skipping the "non-void return value" error for single-line closures a few versions ago, since it was being triggered by the auto-return feature. I think that's what you're seeing here.

Nate

···

On Dec 8, 2015, at 5:55 PM, Tyler Cloutier via swift-evolution <swift-evolution@swift.org> wrote:

Why does that function even compile? For example, the following produces the compiler error: Unexpected non-void return value in void function

[1,2,3,4,5].forEach { x in
    let y = 0
    return x
}

Which is what I would expect. But this compiles just fine:

[1,2,3,4,5].forEach { x in
    return x
}

Is this just a compiler bug, or am I missing something?

Tyler

On Dec 8, 2015, at 2:18 PM, André Videla via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi,

As I understand it, the argument for remove forEach is that it’s easy to make confusing code that doesn’t do what it looks like it should do.

In this particular example:

  func indexOf_foreach(element: Element) -> Int? {
        self.indices.filter { idx in self[idx] == element }.forEach { idx in
            return idx
        }
        return nil
    }

I think the compiler should give us two warnings:
- closure returns type Int when it should return type ()
- function always return nil (which is a unit type just like () ) when it expects Option<Int>

In my opinion those warnings should be enough to make the programmer aware that this code doesn’t do what it looks like it does. Typically, returning a type when () is expected shouldn’t go silently.

As such I think keeping forEach is important as it allows for every elegant expressions and convey already the already established convention when you use forEach that “I”m only interested in side-effects with elements in this collection”

My proposition is then that instead of removing forEach, we should have better type check and warn when the cardinality of the expected type and the returned type don’t match.

-André

On 08 Dec 2015, at 20:07, Chris Eidhof via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@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@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Tyler Cloutier) #11

Ah yep that’s exactly what it is. It seems like having the rule not "apply when the user explicitly wrote a return” would remove a lot of the pain here.

Tyler

···

On Dec 8, 2015, at 4:17 PM, Jordan Rose <jordan_rose@apple.com> wrote:

This falls out of the rule that single-expression closures have their types inferred, and that that inference includes coercion to a Void return type. That's because of code like this:

dispatch_sync(queue) {
  doSomeWork() // returns Bool, but I'm ignoring the result
}

Normally, the closure being passed to dispatch_sync would have an inferred type of '() -> Bool', but dispatch_sync expects a dispatch_block_t, which is '() -> Void'. You can "fix" this by explicitly ignoring the result ("_ = doSomeWork()"), but that's a little silly, so we added a rule that it's okay to ignore the result if the closure's supposed to return Void. That rule really shouldn't apply when the user explicitly wrote a return, though.

Jordan

On Dec 8, 2015, at 15:55, Tyler Cloutier via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Why does that function even compile? For example, the following produces the compiler error: Unexpected non-void return value in void function

[1,2,3,4,5].forEach { x in
    let y = 0
    return x
}

Which is what I would expect. But this compiles just fine:

[1,2,3,4,5].forEach { x in
    return x
}

Is this just a compiler bug, or am I missing something?

Tyler

On Dec 8, 2015, at 2:18 PM, André Videla via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi,

As I understand it, the argument for remove forEach is that it’s easy to make confusing code that doesn’t do what it looks like it should do.

In this particular example:

  func indexOf_foreach(element: Element) -> Int? {
        self.indices.filter { idx in self[idx] == element }.forEach { idx in
            return idx
        }
        return nil
    }

I think the compiler should give us two warnings:
- closure returns type Int when it should return type ()
- function always return nil (which is a unit type just like () ) when it expects Option<Int>

In my opinion those warnings should be enough to make the programmer aware that this code doesn’t do what it looks like it does. Typically, returning a type when () is expected shouldn’t go silently.

As such I think keeping forEach is important as it allows for every elegant expressions and convey already the already established convention when you use forEach that “I”m only interested in side-effects with elements in this collection”

My proposition is then that instead of removing forEach, we should have better type check and warn when the cardinality of the expected type and the returned type don’t match.

-André

On 08 Dec 2015, at 20:07, Chris Eidhof via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@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@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Chris Eidhof) #12

That is a great example of the usefulness, Brent! I like it a lot =).

It seems like I’m the only one who actively dislikes it.

Just to give a little bit more background: I’ve taught some workshops on Swift, and this problem has come up more than once. Before sending my original email, I showed it to a friend in the office, he knew what the return inside forEach was doing, but said it was only because he’d been bitten by the exact same thing.

I liked Joe’s proposal. This might even get us a step closer to writing `while` and `for` in the standard library, rather than have them as built-in constructs…

Chris

···

On 08 Dec 2015, at 17:47, Brent Royal-Gordon <brent@architechies.com> wrote:

As long as the `for` loop is in the language, I don’t really see the use of `forEach`.

Here’s a typical use of forEach for me:

  generateABunchOfSubviews().forEach(addSubview)

Seriously. I have one unreleased app which generates an entire software keyboard like that.

You could write it like:

  for subview in generateABunchOfSubviews() {
    addSubview(subview)
  }

But why use the extra lines and temporary variable when you don’t have to?

--
Brent Royal-Gordon
Architechies


(thorsten@portableinnovations.de) #13

I like that! I've been missing Smalltalk's non-local return myself.

-Thorsten

···

Am 08.12.2015 um 21:21 schrieb Sean Heber via swift-evolution <swift-evolution@swift.org>:

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@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Joe Groff) #14

Another direction you might take this is to make it a type annotation on the function type, like throws, so forEach has a type like this:

func forEach(body: (Element) breaks -> ())

and a closure that `breaks` has nonlocal behavior for break/continue/return (and is implied to be noescape and void-returning, I guess).

-Joe

···

On Dec 8, 2015, at 1:58 PM, Chris Eidhof via swift-evolution <swift-evolution@swift.org> wrote:

I think having a different syntax for returning from an anonymous function would be a bad idea. They’re really the same thing.


#15

This is really interesting. Ruby provides similar functionality with its lambda vs. proc, but a type annotation is much more understandable. It could also imply @noescape automatically:

    func forEach(@canbreak body: Element -> Void)

Stephen

···

On Dec 8, 2015, at 5:13 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

Another direction you might take this is to make it a type annotation on the function type, like throws, so forEach has a type like this:

func forEach(body: (Element) breaks -> ())

and a closure that `breaks` has nonlocal behavior for break/continue/return (and is implied to be noescape and void-returning, I guess).


(thorsten@portableinnovations.de) #16

+1 for keeping forEach and the rationale.

-Thorsten

···

Am 08.12.2015 um 22:56 schrieb Erica Sadun via swift-evolution <swift-evolution@swift.org>:

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 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@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@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@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(David Owens II) #17

Another language construct seems a bit much for this, right? Maybe I’m missing something, but can’t we get the same behavior with an overload?

extension Array {
    func forEach<U>(body: (element: Element) throws -> U?) rethrows -> U? {
        for e in self {
            if let result = try body(element: e) { return result }
        }
        
        return nil
    }
}

func g(e: Int) -> Int? {
    if e == 2 { return e }
    return nil
}

let arr = [1, 2, 3]
arr.forEach { print($0) }
let result = arr.forEach(g)
result // has the value of 2

Now, Swift has some issues determining the types properly if you attempt to inline the g function at the forEach() callsite, but that can be fixed.

-David

···

On Dec 9, 2015, at 4:40 AM, Stephen Celis via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 8, 2015, at 5:13 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

Another direction you might take this is to make it a type annotation on the function type, like throws, so forEach has a type like this:

func forEach(body: (Element) breaks -> ())

and a closure that `breaks` has nonlocal behavior for break/continue/return (and is implied to be noescape and void-returning, I guess).

This is really interesting. Ruby provides similar functionality with its lambda vs. proc, but a type annotation is much more understandable. It could also imply @noescape automatically:

   func forEach(@canbreak body: Element -> Void)

Stephen
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Joe Groff) #18

Another language construct seems a bit much for this, right? Maybe I’m missing something, but can’t we get the same behavior with an overload?

A language construct that helps eliminate multiple other language constructs would potentially be a net win, though. If there were closures that supported nonlocal exits, then `do`, `for...in`, and possibly other constructs could become library functions, and other "block-like" library features like `autoreleasepool`, `withUnsafePointer` would work more naturally too.

-Joe

···

On Dec 9, 2015, at 8:47 AM, David Owens II <david@owensd.io> wrote:

extension Array {
    func forEach<U>(body: (element: Element) throws -> U?) rethrows -> U? {
        for e in self {
            if let result = try body(element: e) { return result }
        }
        
        return nil
    }
}

func g(e: Int) -> Int? {
    if e == 2 { return e }
    return nil
}

let arr = [1, 2, 3]
arr.forEach { print($0) }
let result = arr.forEach(g)
result // has the value of 2

Now, Swift has some issues determining the types properly if you attempt to inline the g function at the forEach() callsite, but that can be fixed.

-David

On Dec 9, 2015, at 4:40 AM, Stephen Celis via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Dec 8, 2015, at 5:13 PM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Another direction you might take this is to make it a type annotation on the function type, like throws, so forEach has a type like this:

func forEach(body: (Element) breaks -> ())

and a closure that `breaks` has nonlocal behavior for break/continue/return (and is implied to be noescape and void-returning, I guess).

This is really interesting. Ruby provides similar functionality with its lambda vs. proc, but a type annotation is much more understandable. It could also imply @noescape automatically:

   func forEach(@canbreak body: Element -> Void)

Stephen
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Sean Heber) #19

I’m obviously a big fan of this approach. :slight_smile: Anything that can move what would have previously been required to be a built in language feature to the library seems like a good thing to me.

l8r
Sean

···

On Dec 9, 2015, at 11:01 AM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 9, 2015, at 8:47 AM, David Owens II <david@owensd.io> wrote:

Another language construct seems a bit much for this, right? Maybe I’m missing something, but can’t we get the same behavior with an overload?

A language construct that helps eliminate multiple other language constructs would potentially be a net win, though. If there were closures that supported nonlocal exits, then `do`, `for...in`, and possibly other constructs could become library functions, and other "block-like" library features like `autoreleasepool`, `withUnsafePointer` would work more naturally too.

-Joe

extension Array {
    func forEach<U>(body: (element: Element) throws -> U?) rethrows -> U? {
        for e in self {
            if let result = try body(element: e) { return result }
        }
        
        return nil
    }
}

func g(e: Int) -> Int? {
    if e == 2 { return e }
    return nil
}

let arr = [1, 2, 3]
arr.forEach { print($0) }
let result = arr.forEach(g)
result // has the value of 2

Now, Swift has some issues determining the types properly if you attempt to inline the g function at the forEach() callsite, but that can be fixed.

-David

On Dec 9, 2015, at 4:40 AM, Stephen Celis via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 8, 2015, at 5:13 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

Another direction you might take this is to make it a type annotation on the function type, like throws, so forEach has a type like this:

func forEach(body: (Element) breaks -> ())

and a closure that `breaks` has nonlocal behavior for break/continue/return (and is implied to be noescape and void-returning, I guess).

This is really interesting. Ruby provides similar functionality with its lambda vs. proc, but a type annotation is much more understandable. It could also imply @noescape automatically:

   func forEach(@canbreak body: Element -> Void)

Stephen
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Guillaume Lessard) #20

If a `for` loop became a library function with special closures, what would happen to labeled break statements?

e.g.
outer: for i in 0..<5 {
  for j in 0..<5 {
    break outer
  }
}

Guillaume Lessard

···

On 9 déc. 2015, at 10:01, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

A language construct that helps eliminate multiple other language constructs would potentially be a net win, though. If there were closures that supported nonlocal exits, then `do`, `for...in`, and possibly other constructs could become library functions, and other "block-like" library features like `autoreleasepool`, `withUnsafePointer` would work more naturally too.