[Review] SE-0105: Removing Where Clauses from For-In Loops

I'd much rather keep `where` for protocol constraints and rename data-filtering in for-in if kept. My suggestions in the proposal are "if", "unless", "until", and "while".

-- E

···

On Jun 24, 2016, at 11:26 AM, Karl Wagner via swift-evolution <swift-evolution@swift.org> wrote:

-1

I've followed this discussion since the beginning, and I feel the usage is clear given that for...in is a *data-driven* loop

I wouldn't mind renaming to "if" if there is some confusion, but I think we use "where" consistently in the language (and Dave Abrahams proposal for closure argument names takes this further), so could be argued that it's already clear.

Perhaps we should use "where" consistently for data-filtering operations, and possibly rename the generic constraint specifier. If we wanted to be really rigorously consistent.

Karl

None of these keywords make sense as a replacement.

if - This only makes sense if it's a condition that's checked before the loop starts and causes the entire loop to be skipped.
unless - This has the same problem as if, though it inverts the boolean condition.
until - This keyword implies that iteration stops when the condition fails, which is a change in behavior from the where clause (and IMO less useful than the current where behavior).
while - Same as until, though it inverts the boolean condition.

I really think we should just keep the where here. I don't think there's any problem using it both here and in protocol constraints. It means basically the same thing in both cases, that the thing being modified applies to all cases (e.g. all types, or all elements of the sequence) where the condition holds true.

-Kevin

···

On Fri, Jun 24, 2016, at 11:03 AM, Erica Sadun via swift-evolution wrote:

> On Jun 24, 2016, at 11:26 AM, Karl Wagner via swift-evolution <swift-evolution@swift.org> wrote:
>
> -1
>
> I've followed this discussion since the beginning, and I feel the usage is clear given that for...in is a *data-driven* loop
>
> I wouldn't mind renaming to "if" if there is some confusion, but I think we use "where" consistently in the language (and Dave Abrahams proposal for closure argument names takes this further), so could be argued that it's already clear.
>
> Perhaps we should use "where" consistently for data-filtering operations, and possibly rename the generic constraint specifier. If we wanted to be really rigorously consistent.
>
> Karl

I'd much rather keep `where` for protocol constraints and rename data-filtering in for-in if kept. My suggestions in the proposal are "if", "unless", "until", and "while".

As detailed in the proposal, the keywords cover the space of continue false, continue true, break true, break false.

-- E

···

On Jun 24, 2016, at 5:33 PM, Kevin Ballard <kevin@sb.org> wrote:

On Fri, Jun 24, 2016, at 11:03 AM, Erica Sadun via swift-evolution wrote:

On Jun 24, 2016, at 11:26 AM, Karl Wagner via swift-evolution <swift-evolution@swift.org> wrote:

-1

I've followed this discussion since the beginning, and I feel the usage is clear given that for...in is a *data-driven* loop

I wouldn't mind renaming to "if" if there is some confusion, but I think we use "where" consistently in the language (and Dave Abrahams proposal for closure argument names takes this further), so could be argued that it's already clear.

Perhaps we should use "where" consistently for data-filtering operations, and possibly rename the generic constraint specifier. If we wanted to be really rigorously consistent.

Karl

I'd much rather keep `where` for protocol constraints and rename data-filtering in for-in if kept. My suggestions in the proposal are "if", "unless", "until", and "while".

None of these keywords make sense as a replacement.

if - This only makes sense if it's a condition that's checked before the loop starts and causes the entire loop to be skipped.
unless - This has the same problem as if, though it inverts the boolean condition.
until - This keyword implies that iteration stops when the condition fails, which is a change in behavior from the where clause (and IMO less useful than the current where behavior).
while - Same as until, though it inverts the boolean condition.

I really think we should just keep the where here. I don't think there's any problem using it both here and in protocol constraints. It means basically the same thing in both cases, that the thing being modified applies to all cases (e.g. all types, or all elements of the sequence) where the condition holds true.

As detailed in the proposal, the keywords cover the space of continue false, continue true, break true, break false.

But the goal of covering all four of these options is arbitrary and inconsistent with the rest of the language. Moreover, it's arbitrary and inconsistent in a way that inflates the apparent magnitude of the problem.

Firstly: There is no need whatsoever for both "true" and "false" versions of each condition. Swift doesn't have `if` and `unless`, or `guard` and `prevent`, or `while` and `until`. It expects you to use the not operator or reverse the sense of your comparisons (`!=` instead of `==`, `>=` instead of `<`, etc.). We can do the same thing here. If `if !` is good enough, then so is `where !`.

Secondly: Yes, `where` and `while` are both available as list operations (`filter` and `prefix(while:)`), but so are `map` and `sorted`, and nobody is suggesting we need those as keywords. (Well, some people have suggested some additional `for` keywords, but they're wrong.) If we feel that `where` is special, there's nothing wrong with offering only `where`.

And I *would* argue that `where` is special. It's special because skipping an element feels less like control flow than terminating a loop. It's special because `where` is not order-dependent, but `while` is. It's special because terminating the loop is a more drastic action. It's special because the `continue` keyword is less clear than the `break` keyword, but is considered an inviolable term of art. It's special because I suspect (but admittedly cannot prove) that `continue` is more frequently used at the top of a loop, controlled by a single conditional statement based on a side-effect-free boolean operation, than `break`.

Think of it this way: We have not accepted the often-proposed `else`-less `guard`, but if we did, there is broad agreement that, absent an "always `return`" rule for simplicity, the most obvious body to infer inside a loop is `continue`. There are reasons for that, and they're the exact same reasons we support `where` but not `while`.

* * *

My review of this proposal follows.

  * What is your evaluation of the proposal?

I think the motivation is weak and do not find it compelling.

The "elevates one style above other related styles" motivation fails to convince me for the reasons described above; "difficult to document separately at its point of use" can be addressed simply by adding a newline before the `where` clause; "hard to breakpoint and debug" is true of any expression, and is easily addressed by extracting that expression into an inner function; and "rarely used" is, in my opinion, more a result of how it is taught and documented than of how useful it is.

In my opinion, the *only* valid argument cited for the removal of `where` is that learners sometimes misunderstand it. I'm very sympathetic to learners, and I would like to improve their experience with this feature. But there are many other ways to help them, like:

* Making sure that "The Swift Programming Language" teaches the use of `for`/`where`.

* Adding help content for language keywords to Xcode and Swift Playgrounds.

* Making syntax adjustments that might improve clarity, like my suggestion that putting the `where` clause next to the variable might clarify that you are filtering the values which will be seen inside the loop.

* Changing the keyword, if a better keyword can be found.

But at this point, measures like these have not been tried, and I have not heard convincing arguments that they will not work.

Meanwhile, the proposal does not address the problems it causes, suggest that they *should* be addressed in a future proposal, or even acknowledge that there *are* any problems. If `where` is removed, none of its alternatives are nearly as good:

* `guard`/`else`/`continue` clutters the logic with completely avoidable boilerplate.

More worryingly, it obscures the intent: "Skip elements like this" becomes "Elements must be like this, and if they're not, go to the next element". Converting a meaning into mechanical instructions to achieve it is not an improvement. If we wanted to do that, we could dispose of `for`/`in` entirely and replace it with:

  var iterator = collection.makeIterator()
  while let elem = iterator.next() { … }

Folks who wrote AppKit code on Tiger would feel right at home.

* `filter(suchThat:)` (or whatever we call it) is dramatically less efficient and has syntactic issues with that particular slot because trailing closures are unavailable.

* `lazy.filter(suchThat:)` lies on a narrow golden path (it's all too easy to forget the `lazy`) and still has the same syntactic issues as a plain `filter`.

We retired `where` clauses from `if` statements because they were a grammatical hack, an unnatural usage forced on us by the desire to support a feature (multiple optional bindings or pattern matches per keyword) that, in hindsight, we realized was less important. There is no similar consideration driving this proposal. Instead, the justification offered is essentially a series of style complaints and an argument that, if you don't try to teach the feature and you don't try to improve it, some people sometimes guess its meaning incorrectly. That's just not enough.

For the reasons above, I urge the core team to reject this proposal.

  * Is the problem being addressed significant enough to warrant a change to Swift?

Quite possibly. But removing `for`/`in`/`where` is an overreaction. When your arm is broken, you put a cast on it; you don't cut it off.

  * Does this proposal fit well with the feel and direction of Swift?

Yes in that it's a clean-up attempt, but no in that it forces additional boilerplate.

  * If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

`for`/`in`/`where` is necessitated by circumstances that are somewhat unique to Swift among the languages I've used, so I don't really have a basis for comparison.

  * How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Participated pretty heavily in previous discussions.

···

--
Brent Royal-Gordon
Architechies

-1 for the same reasons cited by Tony Allevato, Matthew Johnson, Vladimir.S and Ryan Lovelett.

IMO the arguments brought forth are weak, especially concerning readability, understandability and usage. The statistics provided to demonstrate that the feature is not often used don't tell us anything about the reasons why and I would guess that a prominent reason might be that the feature is not well known because it is is not part of the introductinary documentation.

Extending this feature to something more powerful like Scala's for-expressions or LINQ's similar constructs should be backwards compatible with the current usage of `where` IMO.

-Thorsten

···

Am 23.06.2016 um 16:43 schrieb Matthew Johnson via swift-evolution <swift-evolution@swift.org>:

   * What is your evaluation of the proposal?

-1. It removes a useful feature. Swift 3 has already removed enough and the argument in this case feels pretty weak to me.

I have no doubt that *some* people are confused by this feature, but that will be the case for any feature. This feature is very similar to features in many other languages. The potential for confusion here seems pretty low for anyone familiar with SQL, list comprehensions, etc. Those who aren’t familiar with this construct may be delighted once they learn how it works. If this was an entirely novel programming language feature I might be more sympathetic to the learnability / potential for confusion argument, but it isn’t and the argument in this case is weak IMO.

The style argument is also weak. The Swift compiler is *not* the right place to enforce style. Other proposals attempting to enforce style rules have been rejected and this one should as well.

The argument that `guard` is a more complete replacement is true, but it also completely disregards the fact that filtering is by far the most commonly desired behavior. Swift embraces syntactic sugar for common cases in many places. In that respect this feature fits the language very well. Moving the condition into the loop and requiring explicit control flow is significantly *less* clear and readable IMO.

The argument that this feature is not commonly used only considers a relatively small amount of code in very specific domains. It does not provide any analysis of *why* this might be the case. The findings could be due to the domains in question or stylistic preferences of the authors of the code in question. I’ll concede that the authors of the standard library and Carthage are probably familiar with this feature, but outside of this context the feature may still be relatively unknown. Removing a feature on grounds that it is not commonly used without an analysis of *why* is a bad idea, especially in a relatively new language like Swift where many programmers it is targeting are just starting to learn the language.

Finally, I think “breakage fatigue” in Swift 3 is a valid concern. We are making major changes and removing several conveniences. We should not do this more than necessary. This change does not feel necessary.

It may be possible to improve this feature or replace it with something more powerful down the road, but we should not speculatively remove it now on grounds that we might do that later.

   * Is the problem being addressed significant enough to warrant a change to Swift?

No.

   * Does this proposal fit well with the feel and direction of Swift?

No. It removes a very Swifty (IMO) convenience feature without a compelling reason to do so.

   * If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

I have used languages both with and without features similar to `for in where`. This is a very nice feature in languages that have it. I would be disappointed to see Swift remove it without replacing it with something more powerful.

   * How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I followed the discussion thread, read the proposal, read the reviews, etc.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

      * What is your evaluation of the proposal?

-1. I don't think this is really a big deal, but I find where clauses on
for loops to be very elegant, and I'm not convinced by any of the
arguments for why they should go away.

I'm especially not convinced by the argument over the behavior itself
being confusing, e.g. the fact that it's equivalent to a continue rather
than a break. You know what else is equivalent to a continue that this
proposal isn't modifying? `for case pattern in`. If the refutable
pattern does not match, the element is skipped and the for loop
continues to the next. This is precisely how the `where` clause works
(and it's no surprise that the two were introduced at the same time). So
the argument that says we should remove `where` in favor of explicit guard-
continues also implies that we should remove `for case pattern in`,
which I would strongly disagree with.

      * Is the problem being addressed significant enough to warrant a
        change to Swift?

I don't think so. As the proposal argues, this feature is not very widely-
used, at least within open-source code (though as others have pointed
out, there's no data on how much it's used in closed-source code, and
it's plausible that there is far more closed-source Swift code than open-
source). Since it's not widely-used in public, the arguments for
removing it aren't very significant. And the people who do use it tend
to like the feature. And I agree with the people who have pointed out
that it's not the Swift compiler's job to enforce a single coding style.

* How much effort did you put into your review? A glance, a quick
  reading, or an in-depth study?

Reading the proposal and this review thread.

Is the problem being addressed significant enough to warrant a
change to Swift?

No. In fact, I think that even the proposal itself says as much. The
proposal indicates it means to deprecate an available coding
style. It
seems to me, as much as is practicable, style changes should be
enforced
by something other than the Swift compiler.

I in no way intended the proposal to "say as much".

As syntactic sugar, the filtering syntax is
* rarely used in published deployed code,

With no info on how often it's used in closed-source code. Just
looking at a single closed-source project (the Postmates app), we use
the where clause on for loops 7 times, which is more than your
proposal says the stdlib and a random sampling of open source projects
uses combined. And this is just one project, so who knows how much
it's used in other projects.

* hard to discover (although I like others have taught it to promote
   its visibility),

If there's an educational issue here, that should be addressed in the
documentation (i.e. the book), rather than by removing the feature.

* elevates one style (continue if false) above others (continue if
   false, break if true, break if false), which are not expressible
   using similar shorthand,

I completely disagree. Removing this feature is elevating one style (guard-
continue) over another. Keeping the feature is not making any judgement
call about style whatsoever. And as I said above, `for case pattern in`
already has the continue-if-unmatched behavior, so removing this feature
does not mean the for loop no longer has a way to assume continue.

* introduces a fluent style that discourages design comments at the
   point of use,

You can comment where clauses. Just put it on a new line.

* can be difficult to breakpoint during debugging.

This can be said about a lot of things. If I want to breakpoint my where
clause, I can convert it to a guard-continue. Although first I'd try
putting the clause on its own line and seeing if lldb is smart enough to
then break just on the where clause.

I think these are significant issues.

The recommended alternative (using a separate guard) addresses all
these points: better commenting, better breakpointing and debugging,
and fully covers the domain of filtering and early exiting. If
chaining is desired, using filter and prefix(while:) address all
conditions, allow better commenting, etc, and are more self-
documenting.

You can already use a separate guard. The existence of the where clause
on a for loop does not in any way detract from the ability to use a
separate guard.

The fact that some users may be confused by this terminology is not a
reason to remove it from the language. Some users will be confused by
many concepts in programming languages. If that means this is
considered an "advanced" feature, so be it. We should be able to have
a language that has both basic features and advanced features, and
when a new developer comes across a feature they don't understand,
they learn it, and then they know it. This is not an insurmountable
problem.

For the advanced user, filter and prefix are more customizable and
provide greater coverage of cases involving fine control over
sequences.

Then use them when appropriate. In many cases the where clause is
sufficient and more elegant. Using filter and prefix also introduce the
hazard of forgetting to add ".lazy", which means you're allocating a new
array when you don't need to.

I just taught this to a class of newbies last week and exactly zero
of them had trouble with it. I told my TA that we were debating
removing it, and he was horrified. “It is one of the best features
of Swift!” he said. I agree. It is one of the things which gives
Swift character and makes it fun.

I have also taught this construct, which is always a counterpoint to
discoverability issues.

If you step back and ask: "If this feature were not in the language
already, would it be added?", we would have to discuss why "positive
filtering" should be prioritized as it is and if we include it, what
syntax would least confuse users encountering it for the first time.
Surrounded as I am by learner-developers, I recognize that this is a
real stumbling block -- no matter how ubiquitous it is in SQL, for
example -- and have provided examples of both new and experienced
developers being confused.

The feature wasn't in the language, and it was added. The previous
examples of "if this feature were not in the language already" questions
were all about pre-1.0 features. This feature was added later (IIRC in
Swift 2). And as I said above, this feature complements the `for case
pattern in` behavior. `for case pattern in` lets you skip elements based
on a refutable pattern, and `where` lets you skip elements based on a
boolean condition.

-Kevin Ballard

···

On Thu, Jun 23, 2016, at 10:25 AM, Erica Sadun via swift-evolution wrote:

On Jun 23, 2016, at 7:42 AM, Ryan Lovelett via swift-evolution <swift- >> evolution@swift.org> wrote:
On Jun 23, 2016, at 9:07 AM, Tony Allevato via swift-evolution <swift- >> evolution@swift.org> wrote:
On Jun 23, 2016, at 3:02 AM, Jonathan Hull via swift-evolution <swift- >> evolution@swift.org> wrote: