[swift-evolution] [Oversight] Reference types allow mutating methods through generics

T.J. Usiyan griotspeak at gmail.com
Wed May 4 07:28:36 CDT 2016


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 <jordan_rose at 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 at 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 at 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 at 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, 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
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160504/b351df47/attachment.html>


More information about the swift-evolution mailing list