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

Tyler Fleming Cloutier cloutiertyler at aol.com
Sun May 8 02:44:29 CDT 2016


> On May 7, 2016, at 10:39 PM, Dave Abrahams <dabrahams at apple.com> wrote:
> 
> 
> on Sat May 07 2016, Tyler Fleming Cloutier <cloutiertyler-AT-aol.com <http://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.  

This is basically all web applications, or even just saving out to disk.

> 
>> 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 thinking all types, but maybe I’m over looking something here? There are three different ways equality is implemented for a linked list built with value types in this blog post

https://airspeedvelocity.net/2015/07/26/linked-lists-enums-value-types-and-identity/

“We can also implement == for List the way users expect == to behave for value-semantic collections, by comparing the elements"

If it’s the way users expect, why not have == be defined as exactly that? And why not just have that automatically generated?

Incidentally, “indirect” for structs would mean it could be generated for structs as well. Best of all structs that do not contain any mutable reference types would be PureValues™, (Andrew or Matthew correct me if I’m misrepresenting PureValues) and would be able to implement any complex structure, without having to wrap the structs in reference types.

It could be enforce by the compiler that nothing inside of these could change. A function foo which takes array of PureValues, as a parameter would *always* return the same result if it is a pure function, no matter how it uses the array.


> You're almost there.  “Transitive” implies that you are going to look at
> the parts of a type to see if they are also PureValue's.  So which parts
> of the Array struct does one look at, and why?  Just tell me the
> procedure for determining whether a type is a PureValue.


I’m jumping conversations, but shouldn’t this be every value in the Array struct? All internal state as well as all of the elements of the array.


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

I guess same book, but different languages. 

Suffice it to say, that if I were going to create an equality (==) operator for a LinkedList type (or any other complex data structure), implemented with reference types, there’s basically two valid ways to do it. 

1. Recursively or iteratively explore the list and check that every value in the list is the same. (Could this not be auto-generated?)
2. Just compare the references of each list and call it a day.

What we have now are no guarantees that it will be either of those things. My understanding is that your approach would be number two. I don’t think it’s a drastic approach at all, and it is correct in one sense. But in another sense it’s not correct at all. If I read the list in from a file and want to compare it to another one I already have in memory, no dice. I’ll have to have a special function for that type of comparison.

Anyway, I’m not sure I’m adding much to this conversation. I apologize if I am being confusing.

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

> 



-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160508/98a376d5/attachment.html>


More information about the swift-evolution mailing list