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