Class mutation model and value-constrained protocols


(Dave Abrahams) #1

A “mutating” keyword on a protocol method is only useful if
you can reason about the mutation in generic code.

  protocol P {
    mutating func changeMe()
    func noChange() -> Int
  }

In other words, given x of some type conforming to P, it should be
meaningful that this is an error:

  func f<T: P>(x: T) {
    immutable.changeMe() // can't mutate a "let" binding
  }

which means that you shouldn't be able to get around that meaning by
writing:

  func g<T: P>(x: T) {
    var mutable = x
    mutable.changeMe() // OK
  }

Also, you should be able to reason that both of the following print the same
thing twice, for types whose methods have no external side-effects:

  func gg<T: P>(x: T) {
    print(x)
    x.noChange()
    print(x)
  }

  func ggg<T: P>(x: T) {
    print(x)
    var y = x
    y.changeMe()
    print(x)
  }

When T is a class type, it can easily violate *all* of these
expectations. In other words, classes naturally bypass the mutation
model.

If we are going to maintain source stability after Swift 3, it seems
that we either need to address this now, or decide that it won't be
addressed, because of the “viral const” problem.

One idea that Jordan and I have floated is that protocols with mutating
methods should be constrained to applying to non-class types. That
would be a step in the right direction, but, that still leaves cases
like gg able to easily violate expectations when the protocol in
question has no mutating methods.

Another possibility would be to formalize the idea of value semantics in
protocol declarations, so that non-class protocols were only allowed to
apply to values.

It's also possible that I've overestimated the seriousness of the issue
and we actually can afford to postpone thinking about it until after
Swift 4.

Thoughts?

···

--
Dave


(Chris Lattner) #2

There is no chance that we will have time to re-evaluate this for Swift 3.

-Chris

···

On Jul 5, 2016, at 10:53 AM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

When T is a class type, it can easily violate *all* of these
expectations. In other words, classes naturally bypass the mutation
model.

If we are going to maintain source stability after Swift 3, it seems
that we either need to address this now, or decide that it won't be
addressed, because of the “viral const” problem.

One idea that Jordan and I have floated is that protocols with mutating
methods should be constrained to applying to non-class types. That
would be a step in the right direction, but, that still leaves cases
like gg able to easily violate expectations when the protocol in
question has no mutating methods.

Another possibility would be to formalize the idea of value semantics in
protocol declarations, so that non-class protocols were only allowed to
apply to values.

It's also possible that I've overestimated the seriousness of the issue
and we actually can afford to postpone thinking about it until after
Swift 4.


(David Waite) #3

If we are going to maintain source stability after Swift 3, it seems
that we either need to address this now, or decide that it won't be
addressed, because of the “viral const” problem.

IMHO immutable instances and value vs reference allocation are separate concerns. Just like you can have a struct which contains a memory reference or class that provide shared state, you can a class which is immutable and only represent changes via new object instances.

However, mutable methods and property setters do not expose the ability for a class to behave in this manner, as they have an effective inout self which is not exposed to code. There are optimizations which couldn’t be taken with this sort of approach, but it would certainly be valid for a class to implement “value semantics” with language support. There are abstractions available when using classes that are not available using structs/enums, including class clusters and the State pattern with open subclassing.

If it is impossible for a class to implement the requirements of say MutableCollection, then it makes sense to limit it to value types. But I’ll claim thats a limitation of the language’s support for letting people implement said protocols with immutable reference types, one that we might one day wish to allow for.

All that said, even if a class cannot implement MutableCollection today, they still would be in violation of the protocol with no changes - they just wouldn’t get compiler errors as a result. So given the other enthusiastic proposals in flight, I’d consider such compiler errors to be additive.

-DW

···

One idea that Jordan and I have floated is that protocols with mutating
methods should be constrained to applying to non-class types. That
would be a step in the right direction, but, that still leaves cases
like gg able to easily violate expectations when the protocol in
question has no mutating methods.

Another possibility would be to formalize the idea of value semantics in
protocol declarations, so that non-class protocols were only allowed to
apply to values.

It's also possible that I've overestimated the seriousness of the issue
and we actually can afford to postpone thinking about it until after
Swift 4.

Thoughts?

--
Dave

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


(Anton Zhilin) #4

Personally, I'm fine with current state of things.

Firstly, I consider classes like a compatibility feature. They slightly
fall out of Swift type system. One can almost entirely avoid them, except
when interacting with ObjC. (They somehow code in Haskell, right?)

Secondly, when we interact with a struct (record), we expect it not to
mutate itself by default.
On the other hand, classes are black boxes, they represent some entity with
which we interact. It is natural for such entities to perform internal
mutation during operation.

protocols with mutating methods should be constrained to applying to

non-class types
All methods of classes are implicitly mutating, so if something, then
protocols with non-mutating methods should be disallowed for classes.


(Dave Abrahams) #5

There are too many things in that category already... but I guess maybe
“we can't afford to postpone thinking about this” is a moot point in
that context. OK, I'll put it on ice.

Thanks,

···

on Tue Jul 05 2016, Chris Lattner <clattner-AT-apple.com> wrote:

It's also possible that I've overestimated the seriousness of the issue
and we actually can afford to postpone thinking about it until after
Swift 4.

There is no chance that we will have time to re-evaluate this for Swift 3.

--
Dave


(Matthew Johnson) #6

It's also possible that I've overestimated the seriousness of the issue
and we actually can afford to postpone thinking about it until after
Swift 4.

There is no chance that we will have time to re-evaluate this for Swift 3.

There are too many things in that category already... but I guess maybe
“we can't afford to postpone thinking about this” is a moot point in
that context. OK, I'll put it on ice.

Agree. I hope things as important as a mutability model for classes will still give them due consideration after Swift 3 even if it means a breaking change.

I don’t recall any statements regarding Swift 3.x vs Swift 4, but maybe there is still a chance to do some of these things in the Swift 3.x set of releases (where x > 0) without waiting for Swift 4.

I was going to comment on the mutability model for classes, but now that you’re putting it on ice I’ll save my comments until we pick it back up.

···

On Jul 5, 2016, at 4:15 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:
on Tue Jul 05 2016, Chris Lattner <clattner-AT-apple.com> wrote:

Thanks,

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