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

Dave Abrahams dabrahams at apple.com
Sun May 8 00:39:51 CDT 2016


on Sat May 07 2016, Tyler Fleming Cloutier <cloutiertyler-AT-aol.com> wrote:

>> 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.

Sorry, I guess I don't understand what difference it makes that it's
possible to write code that violates my rules.  It's not news to me, as
I'm sure you knew when you posted it.

>>>    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. 

That's a fundamental property of class instances.

> For any distributed application this is no longer true for objects.

That certainly depends on your programming model for distributed
applications.  If you want to try to maintain the illusion that there's
really only one object when you have a pair communicating across a
process or machine boundary, or that a given object travels across
process or machine boundaries, *and* you want to build a Set that
“holds” objects that may live in other processes, then yes, you'll need
a different system.  

> Equality should be used to uniquely define data.

A mutable thing that has no identity apart from its value is a value
type.  Don't use classes for that, or everything breaks down, because a
mutable class always eventually reveals that it's not a value.

> In a non-distributed application comparing references is also an
> implicit comparison of the entire object graph referenced by that
> reference. 

I don't think so.  It's possible for references x and y to have exactly
isomorphic object graphs, but still x !=== y.

> 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. 

It still might not be equality.  Equal things should be effectively
interchangeable (except for parts explicitly designated inessential,
such as an Array's capacity).  As soon as you expose identity, that
falls apart.

> 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.

For all types, or for reference types?  I'd be totally OK with banning
it for reference types.  I'd disagree strongly with banning it for all
types.

> 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.

Yes, although I don't understand a lot of what you're saying and my
instinct is that arguments about “whole object graph” are barking up the
wrong tree, or at least making it more complicated than necessary.

>> There are still useful custom definitions of
>> equality as I have outlined above.
>
> I think I am missing these. Could you provide an example?

,----[ quoting myself ]
| 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.
`----

Array<T> has a custom equality operator; IMO that's indispensable.

-- 
-Dave


More information about the swift-evolution mailing list