> On May 7, 2016, at 3:03 PM, Dave Abrahams <[email protected]> wrote:
> 
> 
> on Sat May 07 2016, Matthew Johnson <matthew-AT-anandabits.com> wrote:
> 
>> This depends on the type. For types representing resources, etc it works just
>> fine. But for models it does not work unless the model subgraph is entirely
>> immutable and instances are unique. 
>> I agree that it isn't a good idea to provide a default that will
>> certainly be wrong in many cases.
> 
> Please show an example of a mutable model where such an equality would
> be wrong.  

Maybe wrong is a little bit too strong a word, but it certainly isn’t the 
behavior people are accustomed to.  I think most people consider model 
instances to be logically equal if their properties are equal regardless of the 
address of the instances in memory.  Reference identity only works as expected 
if the model instances are uniqued in memory.  

let a: NSMutableArray = [1, 2, 3]
let b: NSMutableArray = [1, 2, 3]

let referenceEquality = a === b  // false
let elementEquality = a == b      // true


> 
>>    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”.
>> 
>> As you noted in a followup, not all structs qualify. Structs that whose 
>> members
>> all qualify will qualify. References to a subgraph that doesn't allow for any
>> observable mutation (i.e. deeply immutable reference types) also qualify.
>> 
>> This means the following qualify:
>> 
>> * primitive structs and enums
>> * observable immutable object subgraphs
>> * any type composed from the previous
>> 
>> It follows that generic types often conditionally qualify depending on their
>> type arguments.
>> 
>>    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>?
> 
> You haven't answered this question.  How would you use this protocol?

I think the best example was given by Andy when discussing pure functions.  
Maybe I want to write a generic function and ensure it is pure.  I can only do 
this if I know that any arguments received that compare equal will always 
present the same observable state.  For example, maybe I wish to memoize the 
result.  

I cannot write such a function for all T, and I also cannot write such a 
function for all T that have value semantics if we adopt the “references are 
values” view of the world.  I need an additional constraint that rejects things 
like Array<UIView>.  (T would obviously also be constrained by a protocol that 
exposes the properties or methods my function requires to compute its result)

In general, it would be used where you need to ensure that the result of any 
operation observing the state of any part of the aggregate value will always 
return the same value at any point in the future.  If I observe a[0].foo now I 
know with certainty the result of observing a[0].foo at any point in the 
future.  This aspect of preservation of observed values across time is 
essential to the distinction between Array<LayoutValue> (see below) and 
Array<UIView>.  It doesn’t matter when I observe the frames of the elements of 
Array<LayoutValue>, I will always get the same rects back.  With Array<UIView> 
that is obviously not the case as the frame of the view could be mutated by 
anyone with a reference to the views at any time in between my observations of 
the frame values.

struct LayoutValue {
        frame: CGRect
}


> 
>>            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?
> 
> Again, the key questions are above, asked a different way.
> 
> -- 
> -Dave

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to