[swift-evolution] [Pitch] Collection Type Element Property Observers

Brent Royal-Gordon brent at architechies.com
Thu Mar 30 17:06:50 CDT 2017


> On Mar 30, 2017, at 1:20 PM, Joshua Alvarado via swift-evolution <swift-evolution at swift.org> wrote:
> 
> I think think the gain of functionality isn't there for the addition of this functionality to be added. There are other ways to implement what you are desiring to do without adding it to Swift language.

For instance, wrap your type in something like this:

struct Recorded<Base: RangeReplaceableCollection> {
    init(_ base: Base) {
        self.base = base
        changes = [ Change(newElements: base) ]
    }
    
    fileprivate(set) var base: Base
    fileprivate(set) var changes: [Change]
}

extension Recorded {
    final class Change: Hashable, CustomStringConvertible {
        let subrange: Range<Recorded.Index>?
        let newElements: [Recorded.Iterator.Element]
        
        init<C: Collection>(subrange: Range<Index>? = nil, newElements: C)
            where C.Iterator.Element == Recorded.Iterator.Element
        {
            self.subrange = subrange
            self.newElements = Array(newElements)
        }
        
        static func == (lhs: Change, rhs: Change) -> Bool {
            return lhs === rhs
        }
        
        var hashValue: Int {
            return ObjectIdentifier(self).hashValue
        }
        
        var description: String {
            if let subrange = subrange {
                return "base[\(subrange.description)] = \(newElements.description)"
            }
            else {
                return "base = \(newElements.description)"
            }
        }
        
        func apply(to c: inout Recorded) {
            let subrange = self.subrange ?? c.startIndex ..< c.endIndex
            c.base.replaceSubrange(subrange, with: newElements)
            c.changes.append(self)
        }
    }
    
    mutating func apply(_ changes: [Change]) {
        for change in changes {
            change.apply(to: &self)
        }
    }
    
    func newChanges(since older: Recorded) -> [Change] {
        var changes = self.changes
        
        guard let lastChange = older.changes.last,
               let i = changes.index(of: lastChange) else {
            return changes
        }
        
        let overlapRange = 0 ... i
        precondition(
            older.changes.suffix(overlapRange.count) == changes[overlapRange],
            "self includes old changes not present in older"
        )
        
        changes.removeSubrange(0 ... i)
        return changes
    }
}

extension Recorded: RangeReplaceableCollection {
    subscript(_ i: Base.Index) -> Base.Iterator.Element {
        get {
            return base[i]
        }
        set {
            replaceSubrange(i ..< index(after: i), with: [newValue])
        }
    }
    
    func index(after i: Base.Index) -> Base.Index {
        return base.index(after: i)
    }
    
    var startIndex: Base.Index {
        return base.startIndex
    }
    
    var endIndex: Base.Index {
        return base.endIndex
    }
    
    init() {
        self.init(Base())
    }
    
    mutating func replaceSubrange<C>(_ subrange: Range<Base.Index>, with newElements: C)
        where C : Collection,
        C.Iterator.Element == Base.Iterator.Element
    {
        let change = Change(subrange: subrange, newElements: newElements)
        change.apply(to: &self)
    }
}

(This is begging for Swift 4's conditional conformance feature, which would allow `Recorded` to conform to `RandomAccessCollection` et.al. if the underlying type did.)

Now you have a ready-made change list, and all you need to do is write a `didSet` that runs `newChanges(since: oldValue)` on the new value and figures out what to do with it. That ought to be faster than a full difference calculation from scratch on every `didSet`.

-- 
Brent Royal-Gordon
Architechies

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170330/700d4391/attachment.html>


More information about the swift-evolution mailing list