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

Tyler Fleming Cloutier cloutiertyler at aol.com
Fri May 6 23:22:44 CDT 2016


> On May 6, 2016, at 9:19 PM, Tyler Fleming Cloutier via swift-evolution <swift-evolution at swift.org> wrote:
> 
>> 
>> On May 6, 2016, at 6:54 PM, Dave Abrahams via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> 
>> 
>> on Fri May 06 2016, Matthew Johnson <matthew-AT-anandabits.com <http://matthew-at-anandabits.com/>> wrote:
>> 
>>>    On May 6, 2016, at 7:48 PM, Dave Abrahams via swift-evolution
>>>    <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>> 
>>>    on Thu May 05 2016, Matthew Johnson <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>> 
>>>        On May 5, 2016, at 10:02 PM, Dave Abrahams
>>>        <dabrahams at apple.com <mailto:dabrahams at apple.com>> wrote:
>>> 
>>>        on Thu May 05 2016, Matthew Johnson <matthew-AT-anandabits.com <http://matthew-at-anandabits.com/>> wrote:
>>> 
>>>        On May 5, 2016, at 4:59 PM, Dave Abrahams
>>>        <dabrahams at apple.com <mailto:dabrahams at apple.com>> wrote:
>>> 
>>>        on Wed May 04 2016, Matthew Johnson <matthew-AT-anandabits.com <http://matthew-at-anandabits.com/>> wrote:
>>> 
>>>        On May 4, 2016, at 5:50 PM, Dave Abrahams via swift-evolution
>>>        <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>> 
>>>        on Wed May 04 2016, Matthew Johnson
>>>        <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>> 
>>>        On May 4, 2016, at 1:29 PM, Dave Abrahams via swift-evolution
>>>        <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>> 
>>>        on Wed May 04 2016, Adrian Zubarev
>>>        <swift-evolution at swift.org <mailto:swift-evolution at swift.org>>
>>>        wrote:
>>> 
>>>        Not sure what to think about the enum cases inside a
>>>        protocol (if AnyEnum would
>>>        even exist), it could be a nice addition to the language, but
>>>        this is an own
>>>        proposal I guess.
>>> 
>>>        We should start by adding AnyValue protocol to which all value
>>>        types
>>>        conforms.
>>> 
>>>        Having a way to constrain conformance to things with value semantics
>>>        is
>>>        something I've long wanted. *However*, the approach described is too
>>>        simplistic. It's possible to build classes whose instances have
>>>        value
>>>        semantics (just make them immutable) and it's possible to build
>>>        structs
>>>        whose instances have reference semantics (just put the struct's
>>>        storage
>>>        in a mutable class instance that it holds as a property, and don't
>>>        do
>>>        copy-on-write). 
>>> 
>>>        In order for something like AnyValue to have meaning, we need to
>>>        impose
>>>        greater order. After thinking through many approaches over the
>>>        years, I
>>>        have arrived at the (admittedly rather drastic) opinion that the
>>>        language should effectively outlaw the creation of structs and enums
>>>        that don't have value semantics. (I have no problem with the idea
>>>        that
>>>        immutable classes that want to act as values should be wrapped in a
>>>        struct). The language could then do lots of things much more
>>>        intelligently, such as correctly generating implementations of
>>>        equality.
>>> 
>>>        That is a drastic solution indeed! How would this impact things like
>>>        Array<UIView>? While Array itself has value semantics, the aggregate
>>>        obviously does not as it contains references which usually be mutated
>>>        underneath us. 
>>> 
>>>        Value semantics and mutation can only be measured with respect to
>>>        equality. The definition of == for all class types would be equivalent
>>>        to ===. Problem solved.
>>> 
>>>        Similar considerations apply to simpler wrapper structs such as Weak.
>>> 
>>>        Same answer.
>>> 
>>>        Hmm. If those qualify as “value semantic” then what kind of structs and
>>>        enums
>>>        would not? A struct wrapping a mutable reference type certainly doesn’t
>>>        “feel”
>>>        value semantic to me and certainly doesn’t have the guarantees usually
>>>        associated with value semantics (won’t mutate behind your back, thread
>>>        safe,
>>>        etc).
>>> 
>>>        Sure it does.
>>> 
>>>        public struct Wrap<T: AnyObject> : Equatable {
>>>        init(_ x: T) { self.x = x }
>>>        private x: T
>>>        }
>>> 
>>>        func == <T>(lhs: Wrap<T>, rhs: Wrap<T>) -> Bool {
>>>        return lhs.x === rhs.x
>>>        }
>>> 
>>>        I defy you to find any scenario where Wrap<T> doesn't have value
>>>        semantics, whether T is mutable or not.
>>> 
>>>        Alternately, you can look at the Array implementation. Array is a
>>>        struct wrapping a mutable class. It has value semantics by virtue of
>>>        CoW.
>>> 
>>>        This goes back to where you draw the line as to the “boundary of the
>>>        value”.
>>>        Wrap and Array are “value semantic” in a shallow sense and are capable
>>>        of deep
>>>        value semantics when T is deeply value semantic. 
>>> 
>>>        No, I'm sorry; this “deep-vs-shallow” thing is a fallacy that comes from
>>>        not understanding the boundaries of your value. Or, put more
>>>        solicitously: sure, you can look at the world that way, but it just
>>>        makes everything prohibitively complicated, so why would you want to?
>>> 
>>>        In my world, there's no such thing as a “deep copy” or a “shallow copy;”
>>>        there's just “copy,” which logically creates an independent version of
>>>        everything up to the boundaries of the value. Likewise, there's no
>>>        “deep value semantics” or “shallow value semantics.” 
>>> 
>>>        Equality defines
>>>        value semantics, and the boundaries of an Array value always includes
>>>        the values of its elements. The *only* problem here is that we have no
>>>        way to do equality comparison on some arrays because some types aren't
>>>        Equatable. IMO the costs of not having everything be equatable, in
>>>        complexity-of-programming-model terms, are too high.
>>> 
>>>        Thank you for clarifying the terminology for me. This is helpful. 
>>> 
>>>        I think I may have misunderstood what you meant by “boundary of the
>>>        value”. Do
>>>        you mean that the boundary of an Array value stops at the reference
>>>        identity for
>>>        elements with reference semantics? 
>>> 
>>>    Yes.
>>> 
>>>        If you have an Array whose elements are of an immutable reference type
>>>        that has value semantics would you say the boundary extends past the
>>>        reference identity of an element and includes a definition of equality
>>>        defined by that type?
>>> 
>>>    Yes!
>>> 
>>>        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!!
>>> 
>>>        Both have their place, but the maximum benefit of value semantics
>>>        (purity) 
>>> 
>>>        I don't know what definition of purity you're using. The only one I
>>>        know of applies to functions and implies no side effects. In that
>>>        world, there is no mutation and value semantics is equivalent to
>>>        reference semantics.
>>> 
>>>        I was using it in the sense of “PureValue” as discussed in this
>>>        thread. 
>>> 
>>>    Sorry, this is the first mention I can find in the whole thread, honest.
>>>    Oh, it was a different thread. Joe describes it as a protocol for
>>>    “types that represent fully self-contained values,” which is just fuzzy
>>>    enough that everyone reading it can have his own interpretation of what
>>>    it means.
>>> 
>>>        I was using it to mean values for which no *observable* mutation is
>>>        possible (allowing for CoW, etc). Is there a better term for this than
>>>        purity?
>>> 
>>>    You're still not making any sense to me. A type for which no observable
>>>    mutation is possible is **immutable**. The “write” part of
>>>    copy-on-write is a pretty clear indicator that it's all about
>>>    **mutation**. I don't see how they're compatible.
>>> 
>>> Sorry, I did not write that very clearly. I should have said no observable
>>> mutation *that happens behind your back*. In other words, the only *observable*
>>> mutation possible is local. 
>> 
>> Yeah, but you need to ask the question, “mutation in what?”  The answer:
>> mutation in the value instance.  Then you need to ask, “how do you
>> determine whether there was mutation?”  
>> 
>>> Immutability accomplishes this by simply prohibiting all
>>> mutation. Primitive value types like Int and structs or enums that
>>> only contain primitive value types accomplish this by getting copied
>>> everywhere.
>>> 
>>> 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. 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".
> 
> 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
> 
>>  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, as shown above. Swift makes it appear as though references and values are on the same level in a way that C does not.
> 
> 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*. 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. 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.
> 
>>>    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”.
> 

Thus structs *without references would qualify.

> I also assume that enforcing immutability on an object graph, via CoW or otherwise, would be unfeasible. 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.
>  
>> 
>>>        I don’t mean to imply that it is the *only* valuable
>>>        property. However, it I (and many others) do believe it is an extremely
>>>        valuable
>>>        property in many cases. Do you disagree?
>>> 
>>>    I think I do. What is valuable about such a protocol? What generic
>>>    algorithms could you write that work on models of PureValue but don't
>>>    work just as well on Array<Int>?
>>> 
>>> Array<Int> provides the semantics I have in mind just fine so there wouldn’t be
>>> any.  Array<AnyObject> is a completely different story. With
>>> Array<AnyObject> you cannot rely on a guarantee the objects contained
>>> in the array will not be mutated by code elsewhere that also happens
>>> to have a reference to the same objects.
>> 
>> Okay then, what algorithms can you write that operate on PureValue that
>> don't work equally well on Array<AnyObject>?
>> 
>>> 
>>>        let t = MyClass()
>>>        foo.acceptWrapped(Wrap(t))
>>>        t.mutate()
>>> 
>>>        In this example, foo had better not depend on the wrapped instance not
>>>        getting
>>>        mutated.
>>> 
>>>        foo has no way to get at the wrapped instance, so it can't depend on
>>>        anything about it.
>>> 
>>>        Ok, but this is a toy example. What is the purpose of Wrap? Maybe foo
>>>        passes the
>>>        wrapped instance back to code that *does* have visibility to the
>>>        instance. My
>>>        point was that shared mutable state is still possible here. 
>>> 
>>>    And my point is that Wrap<T> encapsulates a T (almost—I should have let
>>>    it construct the T in its init rather than accepting a T parameter) and
>>>    the fact that it's *possible* to code something with the structure of
>>>    Wrap so that it has shared mutable state is irrelevant.
>>> 
>>> The point I am trying to make is that the semantic properties of Wrap<T> depend
>>> on the semantic properties of T (whether or not non-local mutation may be
>>> observed in this case). 
>> 
>> No they do not; Wrap<T> was specifically designed *not* to depend on the
>> semantic properties of T.  This was in answer to what you said:
>> 
>>>       A struct wrapping a mutable reference type certainly doesn’t
>>>       “feel” value semantic to me and certainly doesn’t have the
>>>       guarantees usually associated with value semantics (won’t
>>>       mutate behind your back, thread safe, etc).
>> 
>> I have been trying to get you to nail down what you mean by PureValue,
>> and I was trying to illustrate that merely being “a struct wrapping a
>> mutable reference type” is not enough to disqualify anything from being
>> in the category you're trying to describe.  What are the properties of
>> types in that category, and what generic code would depend on those
>> properties?
>> 
>>> It certainly isn’t irrelevant to that point.
>>> 
>>>    HTH,
>>> 
>>>        My expectation is a generic aggregate such as Array would have to
>>>        conditionally conform to AnyValue only when Element also conforms to
>>>        AnyValue.
>>> 
>>>        I’m also wondering how such a rule would be implemented while still
>>>        allowing for CoW structs that *do* implement value semantics, but do
>>>        so while using references internally.
>>> 
>>>        I am not talking about any kind of statically-enforceable rule, although
>>>        we could probably make warnings sophisticated enough to help with this.
>>> 
>>>        You said the you have arrived at the opinion that the language should
>>>        “effectively outlaw” structs and enums that do not have value semantics.
>>>        That
>>>        sounded like static enforcement to me. 
>>> 
>>>        The language outlaws certain kinds of inout aliasing without
>>>        providing static enforcement. This is like that.
>>> 
>>>        I did not know this. Now you have me curious. Can you give an example of
>>>        where
>>>        we are able to violate law? I ask mostly because it sounds like there is
>>>        a
>>>        possibility of stumbling into dangerous territory, possibly without
>>>        being aware
>>>        that you have done so.
>>> 
>>>        See “In-out Parameters” in
>>>        https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID362 <https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID362>
>>> 
>>>        Maybe you meant we should allow the compiler to assume value semantics
>>>        for structs and enums despite the fact that it doesn’t statically
>>>        enforce this?
>>> 
>>>        That would be one *consequence* of effectively outlawing it. The library
>>>        could make similar assumptions.
>>> 
>>>        If the compiler can be sophisticated enough to verify value semantics
>>>        statically maybe it would be better to have that mechanism be
>>>        triggered by conformance to AnyValue rather than for all structs and
>>>        enums. Types that conform to AnyValue would receive the benefits of
>>>        the compiler knowing they have value semantics, while other uses of
>>>        structs and enums would remain valid. Best practice would be to
>>>        conform structs and enums to AnyValue whenever possible.
>>> 
>>>        Another possible advantage of this approach would be allowing
>>>        immutable reference types to conform to AnyValue and receive the
>>>        associated benefits such as the generated implementation of equality,
>>>        etc.
>>> 
>>>        -Matthew
>>> 
>>>        -- 
>>>        Dave
>>> 
>>>        _______________________________________________
>>>        swift-evolution mailing list
>>>        swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>        https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>> 
>>>        _______________________________________________
>>>        swift-evolution mailing list
>>>        swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>        https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>> 
>>>        -- 
>>>        Dave
>>> 
>>>        _______________________________________________
>>>        swift-evolution mailing list
>>>        swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>        https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>> 
>>>        -- 
>>>        Dave
>>> 
>>>        -- 
>>>        Dave
>>> 
>>>        _______________________________________________
>>>        swift-evolution mailing list
>>>        swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>        https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>> 
>>>    -- 
>>>    Dave
>>> 
>>>    _______________________________________________
>>>    swift-evolution mailing list
>>>    swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>    https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>> 
>> 
>> -- 
>> Dave
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160506/3f9a4140/attachment.html>


More information about the swift-evolution mailing list