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

Dave Abrahams dabrahams at apple.com
Thu May 5 10:36:20 CDT 2016


on Wed May 04 2016, "T.J. Usiyan" <griotspeak-AT-gmail.com> 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? 

There are two answers:

1. We haven't gotten around to it

2. As I mentioned elsewhere, that doesn't really have any meaning unless
   we also add some semantic restrictions, because a “value type” can have
   reference semantics. 

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

-- 
Dave


More information about the swift-evolution mailing list