[swift-evolution] Should we rename "class" when referring to protocol conformance?

Tyler Fleming Cloutier cloutiertyler at aol.com
Sat May 7 17:08:04 CDT 2016


> On May 7, 2016, at 12:52 PM, Dave Abrahams <dabrahams at apple.com> wrote:
> 
> 
> on Fri May 06 2016, Tyler Fleming Cloutier <cloutiertyler-AT-aol.com> wrote:
> 
>>    On May 6, 2016, at 6:54 PM, Dave Abrahams via swift-evolution
>>    <swift-evolution at swift.org> wrote:
>> 
>>    on Fri May 06 2016, Matthew Johnson <matthew-AT-anandabits.com> wrote:
>> 
>>        On May 6, 2016, at 7:48 PM, Dave Abrahams via swift-evolution
>>        <swift-evolution at swift.org> wrote:
>> 
>>        Swift’s collections also accomplish this through copying, but only when
>>        the
>>        elements they contain also have the same property. 
>> 
>>    Only if you think mutable class instances are part of the value of the
>>    array that stores references to those class instances. As I said
>>    earlier, you can *take* that point of view, but why would you want to?
>>    Today, we have left that question wide open, which makes the whole
>>    notion of what is a logical value very indistinct. I am proposing to
>>    close it.
>> 
>>        On the other hand, it is immediately obvious that non-local mutation
>>        is quite possibly in the elements of a Swift Array<AnyObject> unless
>>        they are all uniquely referenced.
>> 
>>    If you interpret the elements of the array as being *references* to
>>    objects, there is no possibility of non-local mutation. If you
>>    interpret the elements as being objects *themselves*, then you've got
>>    problems.
>> 
>> This does not make sense, because you’ve got problems either way. You are
>> arguing, essentially, that everything is a value type because
>> references/pointers are a value. 
> 
> I am arguing that every type can be viewed as a value, allowing us to
> preserve a sense in which Array<T> has value semantics irrespective of
> the details of T.
> 
>> If that were the case then the *only* valid way to compare the
>> equality of types would be to compare their values. Overriding the
>> equality operator would inherently violate the property of
>> immutability, i.e. two immutable objects can change their equality
>> even without mutation of their “values".
> 
> Not at all.  In my world, you can override equality such that it
> includes referenced storage when either:
> 
> 1. the referenced storage will not be mutated
> 2. or, the referenced storage will only mutated when uniquely-referenced.
> 
>> 
>> func ==(lhs, rhs) {
>> ...
>> }
>> 
>> class MyClass {
>> var a: Int
>> ...
>> 
>> } 
>> 
>> let x = MyClass(a: 5)
>> let y = MyClass(a: 5)
>> 
>> x == y // true
>> y.a = 6
>> x == y // false
> 
> I don't understand what point you're trying to make, here.  I see that x
> and y are immutable. Notwithstanding the fact that the language tries to
> hide the difference between a reference and the instance to which it
> refers from the user (the difference would be clearer if you had to
> write y->a = 6 as in C, but it's still there), that immutability doesn't
> extend beyond the variable binding.  The class instance to which y
> refers, as you've ably demonstrated, is mutable.
> 

The point I’m trying to make is that in the above code, I am able to violate rule 1 of your world insofar as I am including referenced storage in my definition of equality which can be mutated even though my reference is immutable.

>>    Are you arguing that reference types should be equatable by default,
>>    using
>>    equality of the reference if the type does not provide a custom
>>    definition of
>>    equality?
>> 
>>    Yes!!
>> 
>> Custom definitions of equality, inherently, decouple immutability from
>> equality,
> 
> Not a bit.  They certainly *can* do that, if we allow it, but I am
> proposing to ban that.  There are still useful custom definitions of
> equality as I have outlined above.

If you’re proposing to ban that, then I may have misunderstood your position. I think we are in agreement on that, however… (more below)

> 
>> as shown above. Swift makes it appear as though references and values
>> are on the same level in a way that C does not.
> 
> Yep.  It's an illusion that breaks down at the edges and can be really
> problematic if users fully embrace it.  You can't write a generic
> algorithm with well-defined semantics that does mutation-by-part on
> instances of T without constraining T to have value or reference semantics.
> 
> I am not advocating that we require “y->a” for class member access, but
> I *am* suggesting that we should accept the fact that reference and
> value semantics are fundamentally different and make design choices
> (including language rules) accordingly.
> 
>> let x = MyStruct()
>> let y = MyClass()
>> 
>> x.myFoo
>> y.myFoo
>> 
>> vs
>> 
>> my_struct *x = …
>> my_struct y = …
>> 
>> x.my_foo
>> y->my_foo
>> 
>> With C it is explicit that you are crossing a reference. Thus there is only
>> *one* type of equality in C, that the values *are equal*. 
> 
> Well, C doesn't even *have* struct equality;
> http://stackoverflow.com/a/141724
> 
>> This exactly the type of equality you are referring to, but this does
>> not carry over to Swift for precisely the reason that Swift paves over
>> the difference between value and reference types, and then allows you
>> to redefine equality.
>> 
>> Therefore, in essentially no circumstances does it make sense to
>> compare a type by its reference if it has any associated data in
>> Swift. 
> 
> Disagreed.  Instances whose *identity* is significant, i.e. basically
> everything that actually ought to be a class, can be very usefully
> compared by their references.  For example, if equality and hashing were
> defined for UIViews, based on their references, you could use a
> Set<UIView> to keep track of which views had user interaction during a
> given time interval.

This use case is exploiting the fact that the reference is a unique identifier for a view. For any distributed application this is no longer true for objects. Equality should be used to uniquely define data.

In a non-distributed application comparing references is also an implicit comparison of the entire object graph referenced by that reference. When you allow any other definition of equality for reference types, unless that comparison explicitly includes all values in the each of the object’s referenced object graphs, it is only *partial* equality. Thus custom equality is a lie that should probably be expressed with something more like ~=. It’s only equal up to the boundary, which is arbitrarily defined. 

So yes, I agree that at least equality should be consistent with immutability, but in my opinion the only way to accomplish that is to ban custom equality.

I’m of the opinion that there are only two ways of accomplishing this.

EITHER

One could imagine a definition of equality that did explore the entire object graph comparing values (only using references to find other values, not for comparison) as it went. However, this this would not be able to align with the semantics of immutability (maybe by only allowing a single entry point into the graph which was guaranteed to be a unique reference?).

OR

=== should be the only valid equality operator for classes (and you’re right it should be spelled ==), and that if you want to compare classes you should just put all of the data that acts as the “identity” of that class in a value type which can be compared by value. Value types could then have generated equality operators based on the equality of each of their constituent values, some of which could be references (but as I mentioned including references in the identity does not work for distributed applications).

let x = MyClass(…)
let y = MyClass(…)

x.identityStruct == y.identityStruct

As it stands now in Swift, a class is more than just a reference. It also includes all sorts of assumptions about it’s associated data based on the fact that a class “pretends” to include the data it’s associated with. Hence the need for custom equality operators.

I really think we’re on the same page here, probably. Or at least in the same book.

> There are still useful custom definitions of
> equality as I have outlined above.

I think I am missing these. Could you provide an example?

> 
>> Basically, if it will be commonplace to override the equality operator
>> to compare the first level of associated values of a reference type,
>> then the comparison of just the reference has no business being the
>> default.
>> 
>> If the default equality for reference types was defined as the equality of the
>> references it would be inconsistent with the Swift’s current apparent surfacing
>> of the first level of associated data for reference types.
> 
> Yes, but as I've said, that illusion doesn't work in the presence of
> mutability.
> 
>>        I think perhaps what you mean by “purity” is just, “has value
>>        semantics.” But I could be wrong.
>> 
>>        No, an array storing instances of reference types that are not immutable
>>        would
>>        not be “pure” (or whatever you want to call it).
>> 
>>        is derived from deep value semantics. This is when there is no
>>        possibility of shared mutable state. This is an extremely important
>>        property.
>> 
>>        It's the wrong property, IMO.
>> 
>>        Wrong in what sense? 
>> 
>>        Wrong in the sense that it rules out using things like Array that are
>>        logically value types but happen to be implemented with CoW, and if you
>>        have proper encapsulation there's no way for these types to behave as
>>        anything other than values, so it would be extremely limiting. 
>> 
>>        I’m a big fan of CoW as an implementation detail. We have definitely
>>        been
>>        miscommunicating if you thought I was suggesting something that would
>>        prohibit
>>        CoW.
>> 
>>    Then what, precisely, are the syntactic and semantic requirements of
>>    “PureValue?”
>> 
>> I assume what is meant by "PureValue", is any object A, whose own references
>> form a subgraph, within which a change to any of the values would constitute a
>> change in the value of A (thus impermissible if A is immutable). Thus structs
>> would quality as “PureValues”.
> 
> OK, one vote for that interpretation noted.
> 
>> I also assume that enforcing immutability on an object graph, via CoW
>> or otherwise, would be unfeasible. 
> 
> I presume by “enforcing” you mean, “enforcing by the compiler.”  It's
> very easy to enforce that for particular object graphs in library code,
> using encapsulation.

Yes, I do mean by the compiler, but you are right you can enforce this by hiding whatever you’d like behind inaccessible values.

> 
>> You could enforce it on all values accessible by traversing a single
>> reference for reference types, however.
>> 
>> This is why I don’t really buy the argument that there is no such this
>> as deep vs shallow copy. Deep copy means copying the whole “PureValue”
>> or subgraph, shallow copy means traversing a single reference and
>> copying all accessible values.
> 
> Well, again, “you can look at the world that way, but why would you want
> to?”  It makes reasoning about code exponentially more difficult if at
> every level you have to ask whether a copy is deep or shallow.
> 
> -- 
> -Dave



More information about the swift-evolution mailing list