Sorry, I know this is an old thread, but it's really the right place to point this out: the “strict left-to-right evaluation order” of Swift is a nice story we tell, but it isn't true. There's an inconsistency between mutating and non-mutating methods. All I have to do is add mutating
before describe
and the program works just as expected.
The fact that users don't notice the difference and aren't confused is a pretty strong indicator that “strict left-to-right” shouldn't be considered sacred, and the fact that we had to treat inout
differently to make lots of useful programs work is a strong indicator that what we call “strict left-to-right” is in fact the wrong order.
This program demonstrates the differences in one place:
struct Y {
func f<T, U>(_ x: T, _ y: U) -> (T, U) {
print("-", (x, y))
return (x, y)
}
mutating func g<T, U>(_ x: T, _ y: U) -> (T, U) {
print("-", (x, y))
return (x, y)
}
var me: Y {
get {
print("get access")
return self
}
set {
print("set access")
}
}
}
var x = Y()
let a = x.me.f(x.f(1, 2), x.f(3, 4))
print("----------")
let b = x.me.g(x.g(5, 6), x.g(7, 8))
The use of the me
property is to show where acesses are evaluated. The output is
get access
- (1, 2)
- (3, 4)
- ((1, 2), (3, 4))
----------
- (5, 6)
- (7, 8)
get access
- ((5, 6), (7, 8))
set access
(if you use a _modify
accessor it rums where the get
accessor runs below the dashed line. And if you make Y
noncopyable and replace get
with _read
, it runs where the get
accessors run. So it's clearly not using the weird order in the first case due to x.f
being interpreted as creating a closure that contains a copy of x
.)
It's likely the ship has sailed, but IMO the right order is the one you get when everything is mutating. I'd characterize it as left-to-right, inner-to-outer. A left-to-right postorder traversal of the expression tree. It's consistent and non-confusing, and FWIW it's what I intuitively expect a language with specified evaluation order to do.