[proposal] Allow "let" for computed properties which only reference immutable data


(Alexander Momchilov) #1

I came an interesting SO question
<http://stackoverflow.com/a/37161488/3141234> which pointed out a strange
quirk: a computed property must always use the "var" keyword, even if it's
read-only, and only referencing other immutable data.

class Test {
  let hello = "hello"
  let world = "world"
  var phrase: String { //why must this be 'var'?
     return self.hello + self.world
  }}

It would be more appropriate for such a read-only, immutable property, to
use the "let" syntax, so that its immutability is correctly expressed.

Thoughts?


(Sean Heber) #2

I kind of agree with the logic of that, but imagine the following:

class Test {
  let hello = “hello”
  var subject = “world”
  var phrase: String { return hello + “, “ + subject }
}

In this scenario, “subject” can be changed.. and that changes the result of “phrase”. Things like this are why computed properties are “var”.

l8r
Sean

···

On May 11, 2016, at 8:25 AM, Alexander Momchilov via swift-evolution <swift-evolution@swift.org> wrote:

I came an interesting SO question which pointed out a strange quirk: a computed property must always use the "var" keyword, even if it's read-only, and only referencing other immutable data.

class Test {

let hello = "hello"

let world = "world"

var phrase: String { //why must this be 'var'?

return self.hello + self.
world
  
}
}
It would be more appropriate for such a read-only, immutable property, to use the "let" syntax, so that its immutability is correctly expressed.

Thoughts?
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Rod Brown) #3

I believe the reason behind this comes down to the fact that computed values generally won't be used in this way - thus the "computed".

var itself isn't a promise that a value is mutable, so much as 'let' is a promise of immutability. If we allowed this, then the compiler must check that each element of the return value is static and will not change, and in that case, why would you not just store the computer value in a true let to begin with, to avoid doing the work over again on each access?

It seems a somewhat rare and complicated case for the compiler, for little if any benefit.

-Rod

···

On 11 May 2016, at 11:25 PM, Alexander Momchilov via swift-evolution <swift-evolution@swift.org> wrote:

I came an interesting SO question which pointed out a strange quirk: a computed property must always use the "var" keyword, even if it's read-only, and only referencing other immutable data.

class Test {
  let hello = "hello"
  let world = "world"
  var phrase: String { //why must this be 'var'?
     return self.hello + self.world
  }
}
It would be more appropriate for such a read-only, immutable property, to use the "let" syntax, so that its immutability is correctly expressed.

Thoughts?
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Matthew Johnson) #4

I kind of agree with the logic of that, but imagine the following:

class Test {
let hello = “hello”
var subject = “world”
var phrase: String { return hello + “, “ + subject }
}

In this scenario, “subject” can be changed.. and that changes the result of “phrase”. Things like this are why computed properties are “var”.

This example would still be required to be var because it accesses a var in its implementation.

I like the idea of allowing this feature, but only in cases where the compiler can verify immutable semantics. Having it may help drive other features that could expand the cases the compiler can verify. That would be great as this is a direction I would like to see Swift take in the future.

···

Sent from my iPad

On May 11, 2016, at 10:26 AM, Sean Heber via swift-evolution <swift-evolution@swift.org> wrote:

l8r
Sean

On May 11, 2016, at 8:25 AM, Alexander Momchilov via swift-evolution <swift-evolution@swift.org> wrote:

I came an interesting SO question which pointed out a strange quirk: a computed property must always use the "var" keyword, even if it's read-only, and only referencing other immutable data.

class Test {

let hello = "hello"

let world = "world"

var phrase: String { //why must this be 'var'?

return self.hello + self.
world

}
}
It would be more appropriate for such a read-only, immutable property, to use the "let" syntax, so that its immutability is correctly expressed.

Thoughts?
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Joe Groff) #5

Sent from my iPad

I kind of agree with the logic of that, but imagine the following:

class Test {
let hello = “hello”
var subject = “world”
var phrase: String { return hello + “, “ + subject }
}

In this scenario, “subject” can be changed.. and that changes the result of “phrase”. Things like this are why computed properties are “var”.

This example would still be required to be var because it accesses a var in its implementation.

I like the idea of allowing this feature, but only in cases where the compiler can verify immutable semantics. Having it may help drive other features that could expand the cases the compiler can verify. That would be great as this is a direction I would like to see Swift take in the future.

Yeah, this is why we don't currently allow computed "let" properties. We'd only want to do so once we have the language facilities to ensure a computed 'let' property is immutable and has no observable side effects.

-Joe

···

On May 11, 2016, at 8:34 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

On May 11, 2016, at 10:26 AM, Sean Heber via swift-evolution <swift-evolution@swift.org> wrote:

l8r
Sean

On May 11, 2016, at 8:25 AM, Alexander Momchilov via swift-evolution <swift-evolution@swift.org> wrote:

I came an interesting SO question which pointed out a strange quirk: a computed property must always use the "var" keyword, even if it's read-only, and only referencing other immutable data.

class Test {

let hello = "hello"

let world = "world"

var phrase: String { //why must this be 'var'?

return self.hello + self.
world

}
}
It would be more appropriate for such a read-only, immutable property, to use the "let" syntax, so that its immutability is correctly expressed.

Thoughts?
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Alexander Momchilov) #6

Yeah, there will need to be a facility to annotating function purity

···

On Wed, May 11, 2016 at 1:00 PM, Joe Groff <jgroff@apple.com> wrote:

> On May 11, 2016, at 8:34 AM, Matthew Johnson via swift-evolution < > swift-evolution@swift.org> wrote:
>
>
>
> Sent from my iPad
>
>> On May 11, 2016, at 10:26 AM, Sean Heber via swift-evolution < > swift-evolution@swift.org> wrote:
>>
>> I kind of agree with the logic of that, but imagine the following:
>>
>> class Test {
>> let hello = “hello”
>> var subject = “world”
>> var phrase: String { return hello + “, “ + subject }
>> }
>>
>> In this scenario, “subject” can be changed.. and that changes the
result of “phrase”. Things like this are why computed properties are “var”.
>
> This example would still be required to be var because it accesses a var
in its implementation.
>
> I like the idea of allowing this feature, but only in cases where the
compiler can verify immutable semantics. Having it may help drive other
features that could expand the cases the compiler can verify. That would
be great as this is a direction I would like to see Swift take in the
future.

Yeah, this is why we don't currently allow computed "let" properties. We'd
only want to do so once we have the language facilities to ensure a
computed 'let' property is immutable and has no observable side effects.

-Joe

>>
>> l8r
>> Sean
>>
>>
>>> On May 11, 2016, at 8:25 AM, Alexander Momchilov via swift-evolution < > swift-evolution@swift.org> wrote:
>>>
>>> I came an interesting SO question which pointed out a strange quirk: a
computed property must always use the "var" keyword, even if it's
read-only, and only referencing other immutable data.
>>>
>>> class Test {
>>>
>>>
>>> let hello = "hello"
>>>
>>>
>>> let world = "world"
>>>
>>>
>>> var phrase: String { //why must this be 'var'?
>>>
>>>
>>> return self.hello + self.
>>> world
>>>
>>> }
>>> }
>>> It would be more appropriate for such a read-only, immutable property,
to use the "let" syntax, so that its immutability is correctly expressed.
>>>
>>> Thoughts?
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution@swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution@swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution


(Haravikk) #7

Even if a “computed” property were immutable, do we really want to implicitly make it a constant as well? Take for example:

class Test {
    let a:[Int64]
    let b:[Int64]

    init(_ value:Int64) {
        self.a = Array(count: 65536, repeatedValue: Int64(value))
        self.b = Array(count: 65536, repeatedValue: Int64(value &+ 1))
    }

    var combined:[Int64] { return a + b }
}

Each of these arrays is 512kb, so storing a fixed value for the combined property would require another megabyte of space per instance of Test, but if the combined property is only infrequently called on some instances of Test, then it would be a waste of memory to precompute it, especially if you’re dealing with thousands of instances, with only a fraction of them calling the computed property. If you want to accelerate usage of the computed property then that’s what lazy variables are for, as you can delay the expensive combination until you actually need it.

The problem really is that the compiler can only do limited analysis about whether a computer property is used a lot, and in most cases it doesn’t care (it only really cares whether something is unused, as it can try to optimise it away), to make a good decision it would need to run the program with a realistic test-case an analysis memory overhead and performance impact of each method.

So yeah, I’m not really sure of the advantage of this, unless the intention is purely to restrict what can be used within your computed property’s implementation, but I don’t think the compiler can realistically do much in the way of optimising stuff away as precomputed values need to be stored somewhere, which could mean making your type bigger, which begs the question of why you specified a computed value in the first place.

I guess I’m just confused as to what the problem being solved would really be.

···

On 11 May 2016, at 18:00, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On May 11, 2016, at 8:34 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

On May 11, 2016, at 10:26 AM, Sean Heber via swift-evolution <swift-evolution@swift.org> wrote:

I kind of agree with the logic of that, but imagine the following:

class Test {
let hello = “hello”
var subject = “world”
var phrase: String { return hello + “, “ + subject }
}

In this scenario, “subject” can be changed.. and that changes the result of “phrase”. Things like this are why computed properties are “var”.

This example would still be required to be var because it accesses a var in its implementation.

I like the idea of allowing this feature, but only in cases where the compiler can verify immutable semantics. Having it may help drive other features that could expand the cases the compiler can verify. That would be great as this is a direction I would like to see Swift take in the future.

Yeah, this is why we don't currently allow computed "let" properties. We'd only want to do so once we have the language facilities to ensure a computed 'let' property is immutable and has no observable side effects.


(Joe Groff) #8

I kind of agree with the logic of that, but imagine the following:

class Test {
let hello = “hello”
var subject = “world”
var phrase: String { return hello + “, “ + subject }
}

In this scenario, “subject” can be changed.. and that changes the result of “phrase”. Things like this are why computed properties are “var”.

This example would still be required to be var because it accesses a var in its implementation.

I like the idea of allowing this feature, but only in cases where the compiler can verify immutable semantics. Having it may help drive other features that could expand the cases the compiler can verify. That would be great as this is a direction I would like to see Swift take in the future.

Yeah, this is why we don't currently allow computed "let" properties. We'd only want to do so once we have the language facilities to ensure a computed 'let' property is immutable and has no observable side effects.

Even if a “computed” property were immutable, do we really want to implicitly make it a constant as well? Take for example:

class Test {
    let a:[Int64]
    let b:[Int64]

    init(_ value:Int64) {
        self.a = Array(count: 65536, repeatedValue: Int64(value))
        self.b = Array(count: 65536, repeatedValue: Int64(value &+ 1))
    }

    var combined:[Int64] { return a + b }
}

Each of these arrays is 512kb, so storing a fixed value for the combined property would require another megabyte of space per instance of Test, but if the combined property is only infrequently called on some instances of Test, then it would be a waste of memory to precompute it, especially if you’re dealing with thousands of instances, with only a fraction of them calling the computed property. If you want to accelerate usage of the computed property then that’s what lazy variables are for, as you can delay the expensive combination until you actually need it.

Being constant doesn't need to imply "precomputed". I would expect a computed 'let' to still be computed on-demand. 'let' is more important as an API contract; you're guaranteeing to the user that an API won't produce different values if called on the same object at different times.

-Joe

···

On May 11, 2016, at 11:02 AM, Haravikk <swift-evolution@haravikk.me> wrote:

On 11 May 2016, at 18:00, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On May 11, 2016, at 8:34 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

On May 11, 2016, at 10:26 AM, Sean Heber via swift-evolution <swift-evolution@swift.org> wrote:

The problem really is that the compiler can only do limited analysis about whether a computer property is used a lot, and in most cases it doesn’t care (it only really cares whether something is unused, as it can try to optimise it away), to make a good decision it would need to run the program with a realistic test-case an analysis memory overhead and performance impact of each method.

So yeah, I’m not really sure of the advantage of this, unless the intention is purely to restrict what can be used within your computed property’s implementation, but I don’t think the compiler can realistically do much in the way of optimising stuff away as precomputed values need to be stored somewhere, which could mean making your type bigger, which begs the question of why you specified a computed value in the first place.

I guess I’m just confused as to what the problem being solved would really be.


(Chris Lattner) #9

Right. “let” means “always has the same value”, a get-only property means “can not be set” (which is less restrictive). Computed or not isn’t the issue here.

-Chris

···

On May 11, 2016, at 11:31 AM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

   var combined:[Int64] { return a + b }
}

Each of these arrays is 512kb, so storing a fixed value for the combined property would require another megabyte of space per instance of Test, but if the combined property is only infrequently called on some instances of Test, then it would be a waste of memory to precompute it, especially if you’re dealing with thousands of instances, with only a fraction of them calling the computed property. If you want to accelerate usage of the computed property then that’s what lazy variables are for, as you can delay the expensive combination until you actually need it.

Being constant doesn't need to imply "precomputed". I would expect a computed 'let' to still be computed on-demand. 'let' is more important as an API contract; you're guaranteeing to the user that an API won't produce different values if called on the same object at different times.


(Matthew Johnson) #10

I kind of agree with the logic of that, but imagine the following:

class Test {
let hello = “hello”
var subject = “world”
var phrase: String { return hello + “, “ + subject }
}

In this scenario, “subject” can be changed.. and that changes the result of “phrase”. Things like this are why computed properties are “var”.

This example would still be required to be var because it accesses a var in its implementation.

I like the idea of allowing this feature, but only in cases where the compiler can verify immutable semantics. Having it may help drive other features that could expand the cases the compiler can verify. That would be great as this is a direction I would like to see Swift take in the future.

Yeah, this is why we don't currently allow computed "let" properties. We'd only want to do so once we have the language facilities to ensure a computed 'let' property is immutable and has no observable side effects.

Even if a “computed” property were immutable, do we really want to implicitly make it a constant as well? Take for example:

class Test {
    let a:[Int64]
    let b:[Int64]

    init(_ value:Int64) {
        self.a = Array(count: 65536, repeatedValue: Int64(value))
        self.b = Array(count: 65536, repeatedValue: Int64(value &+ 1))
    }

    var combined:[Int64] { return a + b }
}

Each of these arrays is 512kb, so storing a fixed value for the combined property would require another megabyte of space per instance of Test, but if the combined property is only infrequently called on some instances of Test, then it would be a waste of memory to precompute it, especially if you’re dealing with thousands of instances, with only a fraction of them calling the computed property. If you want to accelerate usage of the computed property then that’s what lazy variables are for, as you can delay the expensive combination until you actually need it.

The problem really is that the compiler can only do limited analysis about whether a computer property is used a lot, and in most cases it doesn’t care (it only really cares whether something is unused, as it can try to optimise it away), to make a good decision it would need to run the program with a realistic test-case an analysis memory overhead and performance impact of each method.

So yeah, I’m not really sure of the advantage of this, unless the intention is purely to restrict what can be used within your computed property’s implementation, but I don’t think the compiler can realistically do much in the way of optimising stuff away as precomputed values need to be stored somewhere, which could mean making your type bigger, which begs the question of why you specified a computed value in the first place.

I guess I’m just confused as to what the problem being solved would really be.

It provides semantic clarity in the API contract. If you look at the property now and then look at it again at some point in the future you will get the same value back. 'var { get }' does not allow for that. I imagine we would be able to specify 'let' protocol requirements as well which could be implement with either stored or computed 'let' properties.

···

Sent from my iPad

On May 11, 2016, at 1:02 PM, Haravikk <swift-evolution@haravikk.me> wrote:

On 11 May 2016, at 18:00, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On May 11, 2016, at 8:34 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:
On May 11, 2016, at 10:26 AM, Sean Heber via swift-evolution <swift-evolution@swift.org> wrote:


(Haravikk) #11

Ah, thanks both of you, I misunderstood then.

Hmm, in that case though, other than the simple case given in the original message, it seems like this might be non-trivial to check, as you’d need to trace every method that could be used within the computed property to ensure that no vars are ever accessed.

I proposed a while ago the idea of having the compiler determine whether a method is mutating automatically, as this could do various useful things like issue a warning if a mutating method that doesn’t change anything and similar cases, but it was dismissed at the time due to the complexity of adding such checking. This seems like it might be similar in principle; it seems very reasonable in a self-contained method that doesn’t rely on any others, but the rest of the time it’s hard to check.

I suppose it could be done for these kinds of self-contained computed properties initially though?

···

On 11 May 2016, at 21:04, Chris Lattner <clattner@apple.com> wrote:

On May 11, 2016, at 11:31 AM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

  var combined:[Int64] { return a + b }
}

Each of these arrays is 512kb, so storing a fixed value for the combined property would require another megabyte of space per instance of Test, but if the combined property is only infrequently called on some instances of Test, then it would be a waste of memory to precompute it, especially if you’re dealing with thousands of instances, with only a fraction of them calling the computed property. If you want to accelerate usage of the computed property then that’s what lazy variables are for, as you can delay the expensive combination until you actually need it.

Being constant doesn't need to imply "precomputed". I would expect a computed 'let' to still be computed on-demand. 'let' is more important as an API contract; you're guaranteeing to the user that an API won't produce different values if called on the same object at different times.

Right. “let” means “always has the same value”, a get-only property means “can not be set” (which is less restrictive). Computed or not isn’t the issue here.