on Wed May 04 2016, "T.J. Usiyan" <griotspeak-AT-gmail.com> wrote:
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 <jordan_rose@apple.com> 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 > <swift-evolution@swift.org> 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 <griotspeak@gmail.com> 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 > <swift-evolution@swift.org> 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,
>’, 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
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution