> 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