on Thu May 05 2016, Joe Groff <jgroff-AT-apple.com> wrote: >> On May 5, 2016, at 8:56 AM, Dave Abrahams <[email protected]> wrote: >> >> >> on Wed May 04 2016, Joe Groff <jgroff-AT-apple.com> wrote: >> > >>>> On May 4, 2016, at 5:28 AM, T.J. Usiyan via swift-evolution >>>> <[email protected]> wrote: >>>> >>>> Something about your first paragraph reminded me of a question I've >>>> had for a while. Is there a reasoning behind not being able to >>>> restrict a protocol to value types? One way that this might be >>>> workable is if we could overload protocols for Value vs for >>>> reference. >>> >>> I'm not totally comfortable with this, because references are a kind >>> of value. >> >> We're using the word “value” in 3 different ways now. If we don't sort >> them out, this is going to become impossible to resolve. So let me >> propose some terms: >> >> 1. Joe's sense, i.e. the compiler-implementation-level sense, in which a >> value of type T is essentially what an UnsafePointer<T> points at. >> In this sense, a variable of any type T “has a value.” Even though >> it's not strictly correct (since the term really applies to >> expressions), I propose “rvalue” for this one. >> >> 2. The “value types vs reference types” sense, where every type falls >> into one of two crisp buckets based on how it's declared. I propose >> we always say “reference type” or “value type” for this one. >> >> 3. The “value semantics vs reference semantics” sense, where a type's >> category depends on how it is implemented, and it's possible (though >> inadvisable) to fall outside either bucket. This is the only >> interesting category when you're discussing protocols and >> constraints, and doesn't have any intrinsic relationship to sense >> #2. I propose we always say “value semantics” or “reference >> semantics” for this one. > > I claim that my sense is the same as #2. A reference has all the > properties of a value-type value; it has the additional ability to be > used as a handle to access independent program state. Int isn't any > less of a value because it can be used to reference values in an > array.
It's less of a value in sense #2 because x as Any as? AnyObject is always nil for a value in sense #2. > Rather than try to break the world down according to categories of > types, I think it's more effective to look at the semantics of > *operations*. So called "reference types" have value semantics > operations too; Please define the term “value semantics operation.” I wager there is no such thing. > reassigning a class reference, '+='-ing a pointer, A pointer is a value type in every sense of the word. > and adding or removing elements from an Array of references are all > operations that mutate only the value being operated on, even though > none of these types are strictly "value types". Now you're really blending meanings, or at least being very fuzzy. Your Array of references has a sense #1 value that might not even be altered when you change the elements in the array. It is a sense #2 value by language rules. It almost has sense #3 value semantics but for our inability to compare it with "==", because we don't implicitly get "==" defined in terms of "===" for all classes. Saying something has value semantics is meaningless without a way to determine equivalence that respects the boundaries of the value. The *whole point* of value semantics is that initialization, assignment, parameter passing, and returning all create *independent* instances, with no observable shared state, and that can be read and mutated concurrently in different threads. To even test that, you need to nail down what it means for a value to change, and for that, you need an equality comparison. > -Joe > >>> I can see value in there being some kind of PureValue protocol, for >>> types that represent fully self-contained values, but conforming to >>> that protocol requires a bit more thought than just being a struct or >>> enum, since there are structs that have reference semantics (such as >>> UnsafePointer), and there are hybrid value types that contain >>> references to data that isn't part of the value (an Array<Class>, for >>> instance). >>> >>> -Joe >>> >>>> TJ >>>> >>>> On Tue, May 3, 2016 at 11:02 PM, Jordan Rose <[email protected]> wrote: >>>> Dave and I have pondered this before, and considered that one possible >>>> (drastic) solution is to ban classes from implementing protocols with >>>> mutating members, on the grounds that it’s very hard to write an algorithm >>>> that’s correct for both. >>>> >>>> func removing(_ element: Element) -> Self { >>>> var result = self // not necessarily a copy… >>>> result.remove(element) >>>> return result // not necessarily an independent value >>>> } >>>> >>>> func zapBadElements<C: RangeReplaceableCollection where >>>> C.Generator.Element == Int>(_ nums: inout C) { >>>> // requires inout on ‘nums’ even when it’s a class >>>> for i in nums.indices { >>>> if nums[i] < 0 { >>>> nums.removeAtIndex(i) >>>> } >>>> } >>>> // …because of this. >>>> if nums.lazy.filter { $0 == 0 }.count > 5 { >>>> nums = C() >>>> } >>>> } >>>> >>>> var refCollection: SharedArrayOfSomeKind<Int> = … >>>> // either the variable ‘refCollection’ or the instance of >>>> ‘SharedArrayOfSomeKind’ might be mutated…or both! >>>> zapBadElements(&refCollection) >>>> >>>> There are of course ways to safely use a protocol with mutating >>>> requirements with classes, namely if you only use them for mutation (i.e. >>>> they’re only called from ‘mutating’ members or on ‘inout’ parameters) and >>>> never rely on value copying (no assignment, no returning). Most simple >>>> wrappers around mutating members would fall into this category. >>>> >>>> We didn’t really develop the idea very far yet because there’s been more >>>> pressing things to worry about. I’m bringing it up here because it’s an >>>> important idea that shouldn’t get lost. >>>> >>>> --- >>>> >>>> In lieu of this, I and a few others brought up the “incorrect” behavior of >>>> reassigning ‘self’ in a protocol extension when the model type is a class, >>>> and got shot down. I don’t have those discussions on hand at the moment, >>>> but I remember we deliberately decided to leave protocol extensions the >>>> way they were, allowing them to reassign class references. I think it’s >>>> because it means things like zapBadElements are more likely to work >>>> correctly^W as expected―if you don’t have any other references at the time >>>> you do the mutation, it can work. But yeah, I’m uncomfortable with the >>>> situation we’re in right now. >>>> >>>> Jordan >>>> >>>> >>>>> On May 3, 2016, at 13:09, James Froggatt via swift-evolution >>>>> <[email protected]> wrote: >>>>> >>>>> Thanks for the response, I agree this is currently the best >>>>> solution. Unfortunately, it's not just as simple as just >>>>> implementing each method, since without being able to call super, I >>>>> have to fully reimplement the original behaviour, which at best >>>>> seems like bad practice, and would break in future versions of >>>>> Swift, and at worst could lead to hard-to-detect bugs right now. >>>>> >>>>> To recap for anyone reading, protocol extensions currently apply >>>>> mutating methods unmodified to reference types, as I found trying >>>>> to make a reference-type collection. This results in the compiler >>>>> disallowing ‘let’ when calling these functions, and allows methods >>>>> to reassign the reference ‘self’ to a new object. The best solution >>>>> is to manually implement each method, removing the mutating >>>>> modifier, yet this workaround doesn't extend to generic code. >>>>> >>>>> To fix this behaviour, we would need to distinguish between ‘true’ >>>>> mutating functions, which reassign self, and ‘partially’ mutating >>>>> functions, for use in generics and protocol extensions, which can >>>>> reassign properties only. >>>>> Is there any support for making this change? Or are there any simpler >>>>> solutions? >>>>> >>>>> I did submit a bug report, but I'm pretty sure a decent fix is not >>>>> possible without some evolution of the language regarding the >>>>> mutating keyword, so I'm trying to bring this up here in hope of us >>>>> getting an actual solution. I've changed the title to what I hope >>>>> is something that better reflects the problem; this thread was >>>>> originally titled ‘[swift-evolution] [Bug?] Reference types and >>>>> mutating methods’. >>>>> >>>>> >>>>> PS: I have noticed another side-effect of calling mutating >>>>> functions on my reference-type collection: it seems to trigger >>>>> didChange on properties, even when, upon comparing the new and old >>>>> objects, the reference isn't being changed. I haven't done much >>>>> experimentation with this behaviour; this may be an unexpected >>>>> side-effect of an extension method assigning to self, but it feels >>>>> like it could be undefined behaviour. >>>>> >>>>> From James F >>>>> >>>>> On 30 Apr 2016, at 16:38, T.J. Usiyan <[email protected]> wrote: >>>>> >>>>>> The problem here seems to be with using the default implementation >>>>>> provided. If you override `append` in ObservedArray, the compiler allows >>>>>> it. That seems 'safe' but odd at first. I wouldn't *want* to implement >>>>>> every mutating method, but that is the current solution. I haven't >>>>>> puzzled out the reasoning behind this myself. >>>>>> >>>>>> >>>>>> ``` swift >>>>>> class ObservedArray<T> : ArrayLiteralConvertible { >>>>>> var value: [T] >>>>>> init(value: [T]) { >>>>>> self.value = value >>>>>> } >>>>>> required init() { >>>>>> self.value = [] >>>>>> } >>>>>> >>>>>> required convenience init(arrayLiteral elements: T...) { >>>>>> self.init(elements) >>>>>> } >>>>>> >>>>>> } >>>>>> >>>>>> extension ObservedArray { >>>>>> typealias Index = Int >>>>>> >>>>>> var startIndex: Index { >>>>>> return value.startIndex >>>>>> } >>>>>> >>>>>> var endIndex: Index { >>>>>> return value.endIndex >>>>>> } >>>>>> >>>>>> subscript(position: Index) -> T { >>>>>> return value[position] >>>>>> } >>>>>> >>>>>> } >>>>>> >>>>>> extension ObservedArray : RangeReplaceableCollectionType { >>>>>> typealias Generator = IndexingGenerator<[T]> >>>>>> >>>>>> func generate() -> Generator { >>>>>> return value.generate() >>>>>> } >>>>>> } >>>>>> >>>>>> extension ObservedArray { >>>>>> func replaceRange<C : CollectionType where C.Generator.Element == >>>>>> Generator.Element>(subRange: Range<Index>, with newElements: C) { >>>>>> value.replaceRange(subRange, with: newElements) >>>>>> } >>>>>> >>>>>> func append(newElement: T) { // <- adding this makes it work >>>>>> value.append(newElement) >>>>>> } >>>>>> } >>>>>> >>>>>> let array: ObservedArray<String> = [] >>>>>> array.append("1") >>>>>> >>>>>> >>>>>> ``` >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> On Sat, Apr 30, 2016 at 7:52 AM, James Froggatt via swift-evolution >>>>>> <[email protected]> wrote: >>>>>> I don't believe this has been addressed, please correct me if I'm wrong. >>>>>> >>>>>> --My Situation-- >>>>>> I've recently been working on an observable collection type. Because >>>>>> each stores ‘subscriptions’ to changes that occur, it made sense to me >>>>>> that this should be a reference type, so subscriptions can't be copied >>>>>> with the values themselves. >>>>>> >>>>>> I have made this class conform to RangeReplaceableCollectionType, >>>>>> providing it with all the standard collection functions. I do the >>>>>> following: >>>>>> >>>>>> let array: ObservedArray<String> = [] >>>>>> array.append("1") //Error: Cannot use mutating member on immutable >>>>>> value: ‘array’ is a ‘let’ constant >>>>>> >>>>>> I have to make the reference immutable just to use my new collection >>>>>> type? This is a bit of a deal-breaker. >>>>>> >>>>>> --The Problem-- >>>>>> Mutating methods allow ‘self’ to be reassigned, which is just another >>>>>> way to mutate a value type. However, reassigning ‘self’ has a special >>>>>> meaning to reference types, which is presumably the reason they are >>>>>> disallowed in classes. >>>>>> >>>>>> However, classes can conform to protocols with mutating methods, leading >>>>>> to the compiler disallowing calls to mutating methods for ‘let’ values >>>>>> of type ‘protocol<MutatingProtocol, AnyObject>’, which can be an >>>>>> annoyance in generic code. In addition, classes can inherit mutating >>>>>> methods from protocol extensions, leading to the behaviour I describe >>>>>> above. >>>>>> >>>>>> Is this intentional behaviour? Am I going about this in the wrong way? >>>>>> Or is this really an omission in the language? >>>>>> _______________________________________________ >>>>>> swift-evolution mailing list >>>>>> [email protected] >>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>>>> >>>>> _______________________________________________ >>>>> swift-evolution mailing list >>>>> [email protected] >>>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>> >>>> >>>> _______________________________________________ >>>> swift-evolution mailing list >>>> [email protected] >>>> https://lists.swift.org/mailman/listinfo/swift-evolution >> >> -- >> Dave -- Dave _______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
