> On Jun 10, 2016, at 12:59 PM, Xiaodi Wu via swift-evolution 
> <[email protected]> wrote:
> 
> On Fri, Jun 10, 2016 at 12:30 PM, let var go <[email protected] 
> <mailto:[email protected]>> wrote:
> I respect that anti-goal, but I think being over-rigid about limiting 
> developers' choice of expression is also an anti-goal.
> 
> To me, it is like guard statements vs. if-let statements. Some people find 
> one to be more clear than the other. Often times the best choice depends on 
> the context. Sometimes a guard statement can be re-written as an if-let 
> statement in a way that makes the code more clear, and vice versa.
> 
> The comparison with `guard` and `if` is a little inapt. The introduction of 
> `guard` solved a practical daily issue with `if` that was nicknamed the 
> pyramid of doom, where successive `if let` statements caused code to be 
> severely nested in braces and nearly unreadable. Further, you must exist the 
> scope with `guard`; thus, its use signals an intention not possible with 
> `if`. If, on the other hand, you do not wish to exit the scope, you must use 
> `if`. So in a Venn diagram, there are independent uses for `if` that cannot 
> be fulfilled by `guard`, and uses for `guard` that would be unreadable if 
> rewritten with `if`.
>  
> And different people will inevitably have different personal preferences - 
> their own "style", if you will - and will favor one over the other. But it 
> would be a mistake to force everyone into one box in order to prevent the 
> fracturing of the Swift community into "dialects."
> 
> But most importantly (and this is really the kicker for me) there are times 
> when the "where" syntax provides the maximum amount of clarity in the context 
> of my code, and I don't want to lose that expressive power.
> 
> This is the key and salient point here. Would you be able to share some 
> examples where the `where` syntax provides a clear win in clarity? That would 
> definitely be a huge pro, if it can be used to solve issues in expressiveness 
> much like `guard` allowed elimination of the pyramid of doom.

I’d hate to lose `where` in for-in loops; I use it a lot. My own 2c on it is 
that I think if you look at isolated uses of `where` it’s not adding much 
clarity, but having it can allow you to adopt more-consistent patterns 
overall…which IMHO *does* result in clearer code.

I’ll provide two examples of such “consistent patterns” and how the presence of 
`for`-`in` `where` helps stick with them.

First Pattern: for some simple types I like to do data-driven unit tests — 
here’s a collection of values to test, here’s a basic, “algebraic" property 
that should apply, test it actually does, and so on.

When I write a set of tests like that, I like to have a single variable holding 
the “instances to test” that is shared by all relevant tests; this lets me 
start with only a handful of values, confirm it seems to work, and then have a 
single place to edit once I’m ready to expand the values we test upon.

Such tests might wind up looking like this:

  func testNaiveAncestor() {
     // the root has no ancestor so we simply to skip it:
     for position in testPositions where !position.isRootPosition {
        XCTAssertLessThan(
         position.naiveAncestor(), 
         position
        )
     }
  }

  func testNaiveSuccessor() {
     for position in testPositions {
        XCTAssertGreaterThan(
         position.naiveSuccessor(), 
         position
        )
     }
  }

…where `testPositions` holds the values to test, and the two tests are each 
testing a basic algebraic property. Having `where` available on for-loops makes 
it possible to write these in such a way that the intended parallelism between 
the two is visually-apparent—you can tell they’re the same sort of thing just 
by looking at them—and it does so in a way that makes few assumptions on the 
type-or-contents of `testPositions`. 

So `where` here, IMHO, isn’t *clearly* clearer in `testNaiveAncestor()`, but it 
lets `testNaiveAncestor()` and `testNaiveSuccessor()` (etc.) be 
*systemically*-clearer, as it were.

Second Pattern: relatedly, I find code is much clearer when `guard` is 
only-ever used for early exits and early returns. 

There’s no requirement to *not* use `guard` and `continue` together, but if one 
is willing to stick to `guard` == “early exit / early return” it makes code 
much easier to read and audit.

In a handful of places I have for loops that would need both `continue`-style 
conditionals and also early-exit conditionals; having `where` means I can stick 
to using `guard` for early-exits, whereas without it I’d have extra nesting or 
mixed “early-exit” guard and “continue” guard. 

This is as short as I can make it:

  /// Returns the portion of our frame that *might* be visible on-screen; 
doesn't
  /// handle occlusion, but unlike `approximateWindowRegion` will account for 
  /// clipping done by our superviews.
  ///
  /// - note: `intersectionRegion(with view: UIView)` takes `self.bounds`, 
converts
  ///         it to `view`'s coordinates, intersects with `view.bounds`, then 
converts
  ///         that back to `self`'s coordinates...returning `nil` for any empty 
rects.
  ///
  @warn_unused_result
  public final func approximateVisibleRegion() -> CGRect? {
    // before checking everything, confirm we *have a window* and that our
    // position vis-a-vis the window could *possibly* make sense
    // no window => not visible
    guard let w = self.window else {
      return nil
    }
    // empty "frame-intersect-window" => not visible
    guard let regionInWindow = self.intersectionRegion(with: w) else {
      return nil
    }
    
    // now we prepare to "walk upstream":
    var lastVisitedView = self
    var currentFrame = regionInWindow

    // walk "upstream" (starting from our superview), skipping:
    // - superviews that don't clip-to-bounds
    // - the window (since we already took our intersection with it)
    for upstreamView in self.exclusiveSuperviewSequence() where 
upstreamView.clipsToBounds && upstreamView !== w {
      // finding a nil intersection => not visible, early-exit
      guard let upstreamIntersection = lastVisitedView.intersectionRegion(with: 
upstreamView) else {
        return nil
      }
      lastVisitedView = upstreamView
      currentFrame = upstreamIntersection
    }
    // belt-and-suspenders final steps:
    assert(!currentFrame.isEmpty && !currentFrame.isNull)
    return self.convertRect(
      currentFrame, 
      fromView: lastVisitedView
    ).onlyIfNonEmpty
  }

…and without `where` on `for`-`in` loops, the main `for` loop winds up looking 
like one of these:

    // with `if`:
    for upstreamView in self.exclusiveSuperviewSequence() {
      if upstreamView.clipsToBounds && upstreamView !== w {
        // finding a nil intersection => not visible, early-exit
        guard let upstreamIntersection = 
lastVisitedView.intersectionRegion(with: upstreamView) else {
          return nil
        }
        lastVisitedView = upstreamView
        currentFrame = upstreamIntersection
      }
    }

    // with mixed-guard usage:
    for upstreamView in self.exclusiveSuperviewSequence() {
      guard upstreamView.clipsToBounds && upstreamView !== w else {
        continue
      }
      // finding a nil intersection => not visible, early-exit
      guard let upstreamIntersection = lastVisitedView.intersectionRegion(with: 
upstreamView) else {
        return nil
      }
      lastVisitedView = upstreamView
      currentFrame = upstreamIntersection
      }
    }

…and again neither one is *awful*, but:

- the one with `if` adds another level of nesting (annoying!)
- the one with “guard” has mixed “guard” usage (continue/exit)

…and since I like to stick to early-exit guard—it makes it easier to read if 
“guard == exit method”—I’d have to go with the nesting option, which I just 
don’t like much.

Those are the strongest examples I can find; the rest are all essentially like 
this:

  extension Dictionary {

    // with `where`
    func mapValues<T>(excludingKeys keySet: Set<Key>, @noescape valueMap: 
(Value) -> T) -> [Key:T] {
      guard !keySet.isEmpty else { 
        return self.mapValues(valueMap) 
      }
      var result: [Key:T] = [:]
      for (key,value) in self where !keySet.contains(key) {
        result[key] = valueMap(result)
      }
      return result
    }

    // without `where`, `if`:
    func mapValues<T>(excludingKeys keySet: Set<Key>, @noescape valueMap: 
(Value) -> T) -> [Key:T] {
      guard !keySet.isEmpty else { 
        return self.mapValues(valueMap) 
      }
      var result: [Key:T] = [:]
      for (key,value) in self {
        if !keySet.contains(key) {
          result[key] = valueMap(result)
        }
      }
      return result
    }

    // without `where`, `guard`:
    func mapValues<T>(excludingKeys keySet: Set<Key>, @noescape valueMap: 
(Value) -> T) -> [Key:T] {
      guard !keySet.isEmpty else { 
        return self.mapValues(valueMap) 
      }
      var result: [Key:T] = [:]
      for (key,value) in self {
        guard keySet.contains(key) else { 
          continue 
        }
        result[key] = valueMap(result)
      }
      return result
    }

  }

…where again I don’t like “continue” `guard` and thus would wind up picking the 
`if` variant, which adds another level of nesting (or use `.lazy.filter` and 
trust the compiler’s going to boil away the overhead for me).

So in conclusion, IMHO `where` on a `for`-`in` is a *modest* improvement in 
clarity when considered in isolation, but is handier than it may initially seem 
b/c it can allow for broader overall consistency of style.

I thus would be in favor of keeping `where` on for-in, or if it must be removed 
doing so with the intent to restore some better-designed equivalent shortly 
after removal.

>  
> 
> On Fri, Jun 10, 2016 at 10:17 AM Xiaodi Wu <[email protected] 
> <mailto:[email protected]>> wrote:
> I think this idea--if you don't like it, then you don't have to use it--is 
> indicative of a key worry here: it's inessential to the language and promotes 
> dialects wherein certain people use it and others wherein they don't. This is 
> an anti-goal.
> 
> On Fri, Jun 10, 2016 at 12:10 let var go <[email protected] 
> <mailto:[email protected]>> wrote:
> Leave it in!
> 
> It's a great little tool. I don't use it very often, but when I do it is 
> because I've decided that in the context of that piece of code it does 
> exactly what I want it to do with the maximum amount of clarity.
> 
> If you don't like it, then don't use it, but I can't see how it detracts from 
> the language at all.
> 
> The *only* argument that I have heard for removing it is that some people 
> don't immediately intuit how to use it. I didn't have any trouble with it at 
> all. It follows one of the most basic programming patterns ever: "For all x 
> in X, if predicate P is true, do something." The use of the keyword "where" 
> makes perfect sense in that context, and when I read it out loud, it sounds 
> natural: "For all x in X where P, do something." That is an elegant, 
> succinct, and clear way of stating exactly what I want my program to do.
> 
> I don't doubt that it has caused some confusion for some people, but I'm not 
> sold that that is a good enough reason to get rid of it. It seems strange to 
> get rid of a tool because not everyone understands how to use it immediately, 
> without ever having to ask a single question. As long as its not a dangerous 
> tool (and it isn't), then keep it in the workshop for those times when it 
> comes in handy. And even if there is some initial confusion, it doesn't sound 
> like it lasted that long. It's more like, "Does this work like X, or does 
> this work like Y? Let's see...oh, it works like X. Ok." That's the entire 
> learning curve...about 5 seconds of curiosity followed by the blissful 
> feeling of resolution.
> 
> On Fri, Jun 10, 2016 at 9:32 AM Xiaodi Wu via swift-evolution 
> <[email protected] <mailto:[email protected]>> wrote:
> On Fri, Jun 10, 2016 at 11:23 AM, Sean Heber via swift-evolution 
> <[email protected] <mailto:[email protected]>> wrote:
> > And to follow-up to myself once again, I went to my "Cool 3rd Party Swift 
> > Repos" folder and did the same search. Among the 15 repos in that folder, a 
> > joint search returned about 650 hits on for-in (again with some false 
> > positives) and not a single for-in-while use.
> 
> Weird. My own Swift projects (not on Github :P) use “where” all the time with 
> for loops. I really like it and think it reads *and* writes far better as 
> well as makes for nicer one-liners. In one project, by rough count, I have 
> about 20 that use “where” vs. 40 in that same project not using “where”.
> 
> In another smaller test project, there are only 10 for loops, but even so one 
> still managed to use where.
> 
> Not a lot of data without looking at even more projects, I admit, but this 
> seems to suggest that the usage of “where” is going to be very 
> developer-dependent. Perhaps there’s some factor of prior background at work 
> here? (I’ve done a lot of SQL in another life, for example.)
> 
> That is worrying if true, because it suggests that it's enabling 'dialects' 
> of Swift, an explicit anti-goal of the language.
>  
> 
> I feel like “where” is a more declarative construct and that we should be 
> encouraging that way of thinking in general. When using it, it feels like 
> “magic” for some reason - even though there’s nothing special about it. It 
> feels like I’ve made the language work *for me* a little bit rather than me 
> having to contort my solution to the will of the language. This may be highly 
> subjective.
> 
> l8r
> Sean
> 
> _______________________________________________
> swift-evolution mailing list
> [email protected] <mailto:[email protected]>
> https://lists.swift.org/mailman/listinfo/swift-evolution 
> <https://lists.swift.org/mailman/listinfo/swift-evolution>
> _______________________________________________
> swift-evolution mailing list
> [email protected] <mailto:[email protected]>
> https://lists.swift.org/mailman/listinfo/swift-evolution 
> <https://lists.swift.org/mailman/listinfo/swift-evolution>
> 
> _______________________________________________
> swift-evolution mailing list
> [email protected]
> https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to