Set of protocols

Hi,


I just wanted to share a bit of code I use. Swift does not allow Sets of protocols, so I just wrote my own SetOfProtocol<T> structure. T should be a protocol that adopts HashableProtocol : this is not enforced by the compiler since XCode won't check (yet?) that a non concrete type adopts a protocol.


Please comment and report bugs !


import Swift
public protocol HashableProtocol {
    func isEqualTo(other: HashableProtocol) -> Bool
    var hashValue: Int {get}
}
extension HashableProtocol where Self: Equatable {
    public func isEqualTo(other: HashableProtocol) -> Bool {
        if let o = other as? Self {
            return self == o
        }
        return false
    }
}
private struct Wrapper<T>: Hashable {
    var _value: HashableProtocol
    var value: T {return _value as! T}
    var hashValue: Int {
        return _value.hashValue
    }
    init(_ reference: T) {
        self._value = reference as! HashableProtocol
    }
}
private func == <T> (left: Wrapper<T>, right: Wrapper<T>) -> Bool{
    return left._value.isEqualTo(right._value)
}
public struct SetOfProtocolIndex<T>: ForwardIndexType, Comparable {
    private var index: SetIndex<Wrapper<T>>
    public typealias Distance = SetIndex<Wrapper<T>>.Distance
    @warn_unused_result
    public func advancedBy(n: SetOfProtocolIndex.Distance) -> SetOfProtocolIndex {
        return SetOfProtocolIndex<T>(index: index.advancedBy(n))
    }
    @warn_unused_result
    public func advancedBy(n: SetOfProtocolIndex.Distance, limit: SetOfProtocolIndex) -> SetOfProtocolIndex {
        return SetOfProtocolIndex<T>(index: index.advancedBy(n, limit: limit.index))
    }
    @warn_unused_result
    public func distanceTo(end: SetOfProtocolIndex) -> SetOfProtocolIndex.Distance {
        return index.distanceTo(end.index)
    }
    @warn_unused_result
    public func successor() -> SetOfProtocolIndex {
        return SetOfProtocolIndex<T>(index: index.successor())
    }
}
@warn_unused_result
public func == <T> (lhs: SetOfProtocolIndex<T>, rhs: SetOfProtocolIndex<T>) -> Bool {
    return lhs.index == rhs.index
}
@warn_unused_result
public func < <T> (lhs: SetOfProtocolIndex<T>, rhs: SetOfProtocolIndex<T>) -> Bool {
    return lhs.index < rhs.index
}
@warn_unused_result
public func <= <T> (lhs: SetOfProtocolIndex<T>, rhs: SetOfProtocolIndex<T>) -> Bool {
    return lhs.index <= rhs.index
}
@warn_unused_result
public func >= <T> (lhs: SetOfProtocolIndex<T>, rhs: SetOfProtocolIndex<T>) -> Bool {
    return lhs.index >= rhs.index
}
@warn_unused_result
public func > <T> (lhs: SetOfProtocolIndex<T>, rhs: SetOfProtocolIndex<T>) -> Bool {
    return lhs.index > rhs.index
}
public struct SetOfProtocol<T>: CollectionType, Equatable, ArrayLiteralConvertible {
    private var set: Set<Wrapper<T>>
  
    public init(minimumCapacity: Int) {
        set = Set<Wrapper<T>>(minimumCapacity: minimumCapacity)
    }
  
    public var startIndex: SetOfProtocolIndex<T> {return SetOfProtocolIndex(index: set.startIndex)}
  
    public var endIndex: SetOfProtocolIndex<T> {return SetOfProtocolIndex(index: set.endIndex)}
  
    @warn_unused_result
    public func contains(member: T) -> Bool {return set.contains(Wrapper(member))}
  
    @warn_unused_result
    public func indexOf(member: T) -> SetOfProtocolIndex<T>? {
        guard let index = set.indexOf(Wrapper(member)) else {return nil}
        return SetOfProtocolIndex(index: index)
    }
  
    public mutating func insert(member: T) {set.insert(Wrapper(member))}
  
    public mutating func remove(member: T) -> T? {return set.remove(Wrapper(member))?.value}
  
    public mutating func removeAtIndex(index: SetOfProtocolIndex<T>) -> T {return set.removeAtIndex(index.index).value}
  
    public mutating func removeAll(keepCapacity keepCapacity: Bool = false) {set.removeAll(keepCapacity: keepCapacity)}
  
    public mutating func removeFirst() -> T {return set.removeFirst().value}
  
    public var count: Int {return set.count}
  
    public subscript (position: SetOfProtocolIndex<T>) -> T {return set[position.index].value}
  
    public func generate() -> AnyGenerator<T> {
        var generator: SetGenerator<Wrapper<T>>? = set.generate()
        return anyGenerator {generator?.next()?.value}
    }
  
    public init(arrayLiteral elements: T...) {
        set = Set()
        for member in elements {
            set.insert(Wrapper(member))
        }
    }
  
    public init() {
        set = Set<Wrapper<T>>()
    }
  
    public init<S : SequenceType where S.Generator.Element == T>(_ sequence: S) {
        set = Set()
        for member in sequence {
            set.insert(Wrapper(member))
        }
    }
  
    public init(_ sequence: SetOfProtocol) {set = sequence.set}
  
    @warn_unused_result
    public func isSubsetOf(sequence: SetOfProtocol) -> Bool {return set.isSubsetOf(sequence.set)}
  
    @warn_unused_result
    public func isSubsetOf<S : SequenceType where S.Generator.Element == T>(sequence: S) -> Bool {return set.isSubsetOf(SetOfProtocol(sequence).set)}
  
    @warn_unused_result
    public func isStrictSubsetOf(sequence: SetOfProtocol) -> Bool {return set.isStrictSubsetOf(sequence.set)}
  
    @warn_unused_result
    public func isStrictSubsetOf<S : SequenceType where S.Generator.Element == T>(sequence: S) -> Bool {return set.isStrictSubsetOf(SetOfProtocol(sequence).set)}
  
    @warn_unused_result
    public func isSupersetOf(sequence: SetOfProtocol) -> Bool {return set.isSupersetOf(sequence.set)}
  
    @warn_unused_result
    public func isSupersetOf<S : SequenceType where S.Generator.Element == T>(sequence: S) -> Bool {return set.isSupersetOf(SetOfProtocol(sequence).set)}
  
    @warn_unused_result
    public func isStrictSupersetOf(sequence: SetOfProtocol) -> Bool {return set.isStrictSupersetOf(sequence.set)}
  
    @warn_unused_result
    public func isStrictSupersetOf<S : SequenceType where S.Generator.Element == T>(sequence: S) -> Bool {return set.isStrictSupersetOf(SetOfProtocol(sequence).set)}
  
    @warn_unused_result
    public func isDisjointWith(sequence: SetOfProtocol) -> Bool {return set.isDisjointWith(sequence.set)}
  
    @warn_unused_result
    public func isDisjointWith<S : SequenceType where S.Generator.Element == T>(sequence: S) -> Bool {return set.isDisjointWith(SetOfProtocol(sequence).set)}
  
    @warn_unused_result
    public func union(sequence: SetOfProtocol) -> SetOfProtocol {
        var result = SetOfProtocol()
        result.set = set.union(sequence.set)
        return result
    }
  
    @warn_unused_result
    public func union<S : SequenceType where S.Generator.Element == T>(sequence: S) -> SetOfProtocol {return union(SetOfProtocol(sequence))}
  
    public mutating func unionInPlace(sequence: SetOfProtocol) {set.unionInPlace(sequence.set)}
  
    public mutating func unionInPlace<S : SequenceType where S.Generator.Element == T>(sequence: S) {unionInPlace(SetOfProtocol(sequence))}
  
    @warn_unused_result
    public func subtract(sequence: SetOfProtocol) -> SetOfProtocol {
        var result = SetOfProtocol()
        result.set = set.subtract(sequence.set)
        return result
    }
  
    @warn_unused_result
    public func subtract<S : SequenceType where S.Generator.Element == T>(sequence: S) -> SetOfProtocol {return subtract(SetOfProtocol(sequence))}
  
    public mutating func subtractInPlace(sequence: SetOfProtocol) {set.subtractInPlace(sequence.set)}
  
    public mutating func subtractInPlace<S : SequenceType where S.Generator.Element == T>(sequence: S) {subtractInPlace(SetOfProtocol(sequence))}
  
    @warn_unused_result
    public func intersect(sequence: SetOfProtocol) -> SetOfProtocol {
        var result = SetOfProtocol()
        result.set = set.intersect(sequence.set)
        return result
    }
  
    @warn_unused_result
    public func intersect<S : SequenceType where S.Generator.Element == T>(sequence: S) -> SetOfProtocol {return intersect(SetOfProtocol(sequence))}
  
    public mutating func intersectInPlace(sequence: SetOfProtocol) {set.intersectInPlace(sequence.set)}
  
    public mutating func intersectInPlace<S : SequenceType where S.Generator.Element == T>(sequence: S) {intersectInPlace(SetOfProtocol(sequence))}
  
    @warn_unused_result
    public func exclusiveOr(sequence: SetOfProtocol) -> SetOfProtocol {
        var result = SetOfProtocol()
        result.set = set.exclusiveOr(sequence.set)
        return result
    }
  
    @warn_unused_result
    public func exclusiveOr<S : SequenceType where S.Generator.Element == T>(sequence: S) -> SetOfProtocol {return exclusiveOr(SetOfProtocol(sequence))}
  
    public mutating func exclusiveOrInPlace(sequence: SetOfProtocol) {set.exclusiveOrInPlace(sequence.set)}
  
    public mutating func exclusiveOrInPlace<S : SequenceType where S.Generator.Element == T>(sequence: S) {exclusiveOrInPlace(SetOfProtocol(sequence))}
  
    public var hashValue: Int {return set.hashValue}
  
    public var isEmpty: Bool {return set.isEmpty}
  
    public var first: T? {return set.first?.value}
}
extension SetOfProtocol : CustomStringConvertible, CustomDebugStringConvertible {
    public var description: String {return set.description}
  
    public var debugDescription: String {return set.debugDescription}
}
extension SetOfProtocol {
    public mutating func popFirst() -> T? {
        return set.popFirst()?.value
    }
}
public func == <T> (left: SetOfProtocol<T>, right: SetOfProtocol<T>) -> Bool {
    return left.set == right.set
}

The basic idea seems reasonable, but there are a couple of aspects that raise suspicions for me here. Most directly, it seems like a code smell to try to exactly mirror APIs from the stdlib. Those APIs are still a moving target, so you're implicitly signing on to a lifetime of manually checking for changes at every release and adjusting your mirror API to match. You could just drop the entire SetOfProtocol layer and use Set<Wrapper<T>> directly. It's not pristine, but it's not terrible, either.


The other thing that bothers me about this is that it's a lot of hoo-hah to try to get Swift to behave as if it did something it just doesn't do -- namely, treating protocols with associated types as free-standing types. Are you sure your application couldn't be phrased more simply in terms that Swift would support more naturally?

I agree, clearly this code is a mirror of stdlib API. Yes, it might very well need an update each time Set structures are updated. But we can bet that it won't take many updates before Sets are mature and don't change anymore (hopefully allowing protocol types). Meanwhile, there is only one file to update, which will be quick. Using Set<Wrapper<T>> is very ugly. And ugly means more code to type and more difficult to read, a lot of manual wrapping and unwrapping all over the place in many files. And that means more bugs and more coding time.


But I disagree when you write that this code tries "to get Swift to behave as if it did something it just doesn't do --namely, treating protocols with associated types as free-standing types". No one here tries to put protocols with associated types in a container. This makes no sense. Protocols with associated types can only be used as type constraints. The protocols we are talking about here are the other kind, the one that can be used as concrete types all over, including Arrays. And those are 'free-standing'. They can be used as vars, they can be put in Arrays, etc...


Actually, the point is : it doesn't make sense that you can put some protocols in some containers (such as Arrays), (which by the way is a very powerful feature) and not in others (such as Sets). Yes, you can always try and get technical and find some justification to this : the compiler can't check that a protocol adopts another one because blah, blah blah... But there must be a way to do it elegantly and I hope Apple developpers will work to figure it out.

But I disagree when you write that this code tries "to get Swift to behave as if it did something it just doesn't do --namely, treating protocols with associated types as free-standing types". No one here tries to put protocols with associated types in a container...


That's a fair point. This could certainly be used with non-Equatable protocols, though you do explicitly provide for smuggling in Equatable items as well:


extension HashableProtocol where Self: Equatable { ... }


But my underlying point - that this project is all about fighting the standard library - remains valid. I'd say your beef is really with the definition of Equatable. Having to write your own Set wrapper is just a side effect of having written your own version of Equatable. Your set of shadow implementations of the standard library is apt to expand as you come up against other contexts where Equatable objects are expected.


I don't disagree that this whole area is a big mess, at least from the standpoint of elegance and consistency. We're drifing increasingly far from the goal of "C++, only without all those weird consequences and special cases". I'm just skeptical that it's possible to resolve those headbutts without making some performance compromises.

Set of protocols
 
 
Q