On Fri, Jan 22, 2016 at 1:27 PM Joe Groff via swift-evolution < swift-evolution@swift.org> wrote:
On Jan 22, 2016, at 10:21 AM, David Farler <dfarler@apple.com> wrote:
On Jan 22, 2016, at 9:45 AM, Joe Groff <jgroff@apple.com> wrote:
On Jan 22, 2016, at 9:26 AM, David Farler via swift-evolution < > swift-evolution@swift.org> wrote:
Hello everyone,
I'd like to reconsider SE-0003 for Swift 3 and propose cancelling the
change in its entirety. After collecting feedback since Swift's open source
launch, I no longer feel this is a good move and there are a few reasons
why.
There are two main patterns that the removal penalizes:
- Get-Modify-Reassign
- Get-Modify-Return
I've found that many of the problems with this proposal stem from the uses
before and after the "Modify" part, before returning or reassigning with
the new value.
I've seen a few common responses to the var removal. Consider a
`Rectangle` struct:
struct Rectangle {
var origin: (x: Double, y: Double)
var size: (width: Double, height: Double)
}
Even with mutable variables `origin` and `size`, this pattern would be
impossible:
var selection = getRectangularSelection()
if var rect = selection?.rect {
// Mutate `rect` ...
selection.rect = rect
}
These examples don't make sense to me. None of them mutate the 'if var
rect' binding at all. Are you sure this is what you meant?
I abbreviated there with // Mutate `rect.
Ah, sorry, missed that. Years of bad documentation have trained my brain
to blank out comments.
-Joe
So, one might shadow the variable, which is not ideal:
var selection = getRectangularSelection()
if let rect = selection?.rect {
var rect = rect // Not so great
// Mutate `rect` ...
selection.rect = rect
}
Or, you might make a transformation function on `Rect`:
struct Rectangle {
var origin: (x: Double, y: Double)
var size: (width: Double, height: Double)
func withOrigin(x: Double, y: Double) -> Rect {
var r = self
r.origin = (x, y)
return r
}
}
This is a much better solution than shadowing but you would need one of
these for any property that you want to mutate and I think you'll agree
that it doesn't scale with the language we have today. This response begs
for a kind of initializer that takes all of the fields of the original
struct except any that you want to override:
if let rect = selection?.rect.with(origin: newOrigin) {
// ...
}
You can approximate this today without any new language features:
protocol Updatable {}
extension Updatable {
func with<T>(change: (inout T) -> ()) -> T {
var update = value
change(&update)
return update
}
}
if let rect = selection?.rect.with { $0.origin = newOrigin } {
}
I think this approach generally leads to cleaner code, since it's not
forcing you to bind names to otherwise uninteresting intermediate values.
It's definitely more explicit but I don't know if I agree that it's
cleaner with respect to the mutations themselves – it has a lot of the same
shape. I also worry about performance with this pattern as is.
Straw syntax, but maybe you'll see something along these lines on
swift-evolution in the future, which would provide a clear alternative to
direct mutation patterns. Even then, I think having complementary patterns
in the language isn't a bad thing.
These problems come up with the other variable bindings but the one that
ended up bothering me the most was `guard var`:
func transform(selection: Rect?) {
guard let rect = selection else { return }
var _rect = rect
// Mutate `_rect` ...
}
One of the guard statement's main purposes is to conditionally bind a
value as a peer in its own scope, not an inner scope like if statements.
Not having var makes the guard statement much weaker.
There is certainly a bit of confusion about the nuances between value and
reference semantics, who owns a value and when, how effects are propagated
back to values, but I think we can attack the problem with more finesse.
Value types are one of the attractive features of Swift – because of their
semantics, mutating algorithms are written in a familiar style but keeping
effects limited to your unique reference. I don't think we should give that
up now to address confusion about semantics, out of principle, or in
anticipation of new language features. I propose cancelling this change for
Swift 3 and continue to allow `var` in the grammar everywhere it occurs in
Swift 2.2.
I disagree. We have a lot of evidence that 'if var' confuses people—a lot
of users think that 'if var' and 'var' bindings in case patterns will write
back to the original value when this isn't the case. Classes make this
worse, since 'if var' *will* seem to work that way when projecting through
optional class references, and the value vs reference semantics divide is
confusing enough as it is.
I totally agree that the subtlety of the semantics easily gets lost but I
think we can come up with better ways to make them clearer without blowing
away a whole class of syntax. I see this confusion as more of a holistic
indictment of how we express the semantics rather than just the syntax of
`var`. I just don't feel like removing this now is a slam dunk.
Those of us who do understand the semantics don't save anything either—I
at least have to mentally audit any code I see using 'if var' to ensure
that writeback wasn't intended by the original author. Code is read and
maintained more often than it's written, and it's written by more novices
than experts, and 'if var' and its friends feel like very
expert-writer-centric features to me.
-Joe
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution