on Mon May 16 2016, Matthew Johnson <swift-evolution@swift.org> wrote:
>> On May 16, 2016, at 1:39 AM, Dave Abrahams <dabrah...@apple.com> wrote: >> >> >> on Sun May 15 2016, Tyler Fleming Cloutier <cloutiertyler-AT-aol.com >> <http://cloutiertyler-at-aol.com/>> wrote: >> > >>> On May 15, 2016, at 11:48 AM, Dave Abrahams via swift-evolution >>> <swift-evolution@swift.org> wrote: >>> >>> on Mon May 09 2016, Matthew Johnson <matthew-AT-anandabits.com> wrote: >>> >>> On May 8, 2016, at 1:51 AM, Dave Abrahams <dabrah...@apple.com> >>> wrote: >>> >>> on Sat May 07 2016, Andrew Trick <atrick-AT-apple.com> wrote: >>> >>> On May 7, 2016, at 2:04 PM, Dave Abrahams >>> <dabrah...@apple.com> wrote: >>> >>> 2. Value types are not "pure" values if any part of >>> the aggregate >>> contains a >>> reference whose type does not have value semantics. >>> >>> Then Array<Int> is not a “pure” value (the buffer >>> contained in an >>> Array<Int> is a mutable reference type that on its own, >>> definitely does >>> *not* have value semantics). I don't think this is what >>> you intend, and >>> it indicates that you need to keep working on your >>> definition. >>> >>> It sounds like you’re changing the definition of value >>> semantics to make it >>> impossible to define PureValue. >>> >>> Not on purpose. >>> >>> Does Array<T> have value semantics then only if T also has >>> value >>> semantics? >>> >>> This is a great question; I had to rewrite my response four >>> times. >>> >>> In my world, an Array<T> always has value semantics if you >>> respect the >>> boundaries of element values as defined by ==. That means that >>> if T is >>> a mutable reference type, you're not looking through references, >>> because >>> == is equivalent to ===. >>> >>> Therefore, for almost any interesting SomeConstraint that >>> doesn't refine >>> ValueSemantics, then >>> >>> Array<T: SomeConstraint> >>> >>> only has value semantics if T has value semantics, since >>> SomeConstraint >>> presumably uses aspects of T other than reference identity. >>> >>> The claim has been made that Array always has value >>> semantics, >>> implying that the array value’s boundary ends at the >>> boundary of it’s >>> element values. >>> >>> Yes, an array value ends at the boundary of its elements' values. >>> >>> That fact is what allows the compiler to ignore mutation of >>> the >>> buffer. >>> >>> I don't know what you mean here. >>> >>> It's perfectly clear that Array<T> is a PureValue iff T is a >>> PureValue. >>> PureValue is nothing more than transitive value semantics. >>> >>> 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. >>> >>> We look at the observable parts. >>> >>> That begs the question. The “parts” of an Array are the observable >>> features that are considered by equality. >>> >>> We do not look at unobservable parts because we want flexibility to >>> use things like CoW, shared immutable references, etc in our >>> implementation. >>> >>> IMO the important thing when it comes to functional purity is not what >>> you *can* observe, but what you *do* observe. >>> >>> Can you share your definition of value semantics? >>> >>> Explaining it well and in sufficient detail for this discussion takes >>> some doing, but I think John Lakos and I share an understanding of value >>> semantics and he has a really detailed explanation in >>> https://www.youtube.com/watch?v=W3xI1HJUy7Q and >>> https://www.youtube.com/watch?v=0EvSxHxFknM. He uses C++ in places, >>> but it's not particularly advanced, and the fundamental ideas apply just >>> as well to Swift. >>> >>> Super interesting talk! >>> >>> But consider: isn't a single value type able to represent *multiple* >>> ethereal types? >> >> “ethereal?” Does he really use that term? I don't know what it means. >> >>> >>> std::vector is a good example. What are the salient attributes of this >>> type? In the talk John says that >>> >>> 1. the size is >>> 2. the values in the vector are >>> 3. the capacity, however *is not* >> >> Yup, just like Array. Thus the equality test for arrays ignores >> capacity. >> >>> in which case std::vector would be an approximation of an ethereal >>> type which has a list of values, and the capacity is just an artifact >>> of the approximation. But you could also imagine an ethereal type >>> which *does* depend of the capacity of the object, and std::vector >>> unwittingly approximates that type too! In this case someone, >>> unfamiliar with the implementation might use it under the assumption >>> that capacity *is* part of the ethereal type and by extension the >>> equality of std::vector. >>> >>> John avoids the problem by saying that this must specified in the >>> documentation. >> >> Yes. >> >>> I tend to see this as breaking encapsulation since you need to know >>> the implementation of the equality operator to be able to determine if >>> a public property, the capacity, is part of the ethereal type. >> >> No, you just need documentation. >> >>> It’s not always the case that you have access to either the >>> documentation or the implementation. >> >> Without the documentation, you're lost. We go a lot further with naming >> conventions in Swift than typical C++ does, but even in Swift you can't >> expect to fully understand semantics without documentation. >> >>> This implies, therefore, that if salient attributes *define* the >>> immutability of the value type, then the public interface is not >>> guaranteed to be immutable, since it is allowed to include non-salient >>> attributes. For example, a vector’s capacity could change at any time, >>> by virtue of it being stored via a reference. >>> >>> What I am saying is that a PureValue is a value type whose public >>> interface comprises *only* salient attributes. And I also claim that >>> this is a useful distinction amongst value types. >> >> Then Array<Int> is not a PureValue because it exposes capacity?! That >> sounds crazy to me, since the Array's capacity in no sense has reference >> semantics. >> >>> John also says that a salient attribute must derive *only* from the >>> state of a particular instance of a type. This by extension implies >>> that a salient attribute must derive exclusively from pure >>> values. However, this also means that without some “indirect” keyword, >>> PureValues are restricted to acyclic and non-recursive structures. >>> >>> I also claim that equality can be automatically generated for >>> PureValues by equating each of there salient attributes. >> >> That's true for almost any value, provided we define equality for >> reference types properly. >> >>> I really apologize if this seems like rambling again, but I am very >>> interested in this problem. >> >> I'm glad you are! Few programmers dig far enough to understand value >> semantics at a deep level. >> >> All that said, I still think PureValue is a red herring. Unless I'm >> forgetting something that happened in the thread two weeks ago, nobody >> has shown me code that relies on PureValue but could not equally well be >> written by using a Value constraint. > > Looking forward to hearing your thoughts on the code I just posted. > > In addition to that, I think it is also important to note that > contained within the notion of PureValue is thread safety. For > example, in CSP PureValues would can be sent as messages on a channel. > Clearly sending Array<MutableReferenceType> over a channel is > something you would not do in CSP. Not if you were interested in anything other than the identities of the elements. But I can easily imagine representing a selection as a Set<T> and passing that across threads, where T might be a mutable reference type (using my default definition of == and hashValue for all references). > You can also freely share a ImmutableBox<MyPureValue> and read from it > in any thread you wish without any trouble. You cannot do that with > ImmutableBox<Array<MutableReferenceType>>. In the latter, you cannot > mutate the array, but you can still mutate the objects it contains > references to. Again, it depends on what you look at. We could create a value type that distinguishes object identities, e.g. struct Identity<SomeClass : AnyObject> : Hashable, Comparable { let subject: SomeClass init(subject: SomeClass) { self.subject = subject } ... } func == <X>(lhs: Identity<X>, rhs: Identity<X>) -> Bool { return lhs === rhs } then you could write: ImmutableBox<Array<Identity<MutableReferenceType>>> But would you really make it *impossible* to observe the subject of an Identity? We could, but I'm not sure I would. It would come at a cost to efficiency in those places where you have a collection of Identity's and you happen to know they're not shared across threads, which I think is probably an important use-case. >> >>> Tyler >>> >>> It may be helpful >>> if we start there and refine your definition to exclude impure value >>> types like Array<UIView>. >>> >>> In the meantime I’ll take another shot: >>> >>> 1. Scalars are pure values. >>> >>> 2. Any aggregate type with value semantics is a pure value iff all >>> observable parts of the aggregate are pure values. >>> >>> -- >>> -Dave >>> _______________________________________________ >>> swift-evolution mailing list >>> swift-evolution@swift.org >>> https://lists.swift.org/mailman/listinfo/swift-evolution >>> >> >> -- >> -Dave > > _______________________________________________ > swift-evolution mailing list > swift-evolution@swift.org > https://lists.swift.org/mailman/listinfo/swift-evolution -- -Dave _______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution