[swift-users] ⁨Is it possible to store a set of heterogeneous items with protocol?

Vladimir.S svabox at gmail.com
Tue Jul 18 12:33:10 CDT 2017


On 17.07.2017 4:51, Glen Huang via swift-users wrote:
> Thanks for the code sample and link, but if I’m not wrong, this pattern doesn’t allow 
> heterogeneous items.

Support the question. Trying to understand if we can have something like 
[AnyHashable] for our custom protocol(with associated type) or AnyHashable has a very 
special support from compiler and we can use only [Any] or such kind of wrapper:

struct AnyMyProtocol {
   let actualInstance: Any
   init<T: MyProtocol>(_ instance: T) { actualInstance = instance}
}

let instances: [AnyMyProtocol] = [AnyMyProtocol(...), AnyMyProtocol(...)]

if let some = instances[0].actualInstance as? SpecificImplementationOfMyProtocol {
	// use 'some' as SpecificImplementationMyProtocol instance
	// seems like no way to refer to just MyProtocol
}


> 
> If I have these definitions:
> 
> struct Chicken {}
> struct Pig {}
> 
> class ChickenFarm: Farm {
>      func grow() -> Chicken {
>          return Chicken()
>      }
> }
> 
> class PigFarm: Farm {
>      func grow() -> Pig {
>          return Pig()
>      }
> }
> 
> Then:
> 
> var farms = // How do I define a set that can contain both ChickenFarm and PigFarm?
> farms.insert(AnyFarm<Chicken>(ChickenFarm()))
> farms.insert(AnyFarm<Pig>(PigFarm()))
> 
> 
>> On 17 Jul 2017, at 4:02 AM, Nevin Brackett-Rozinsky 
>> <nevin.brackettrozinsky at gmail.com <mailto:nevin.brackettrozinsky at gmail.com>> wrote:
>>
>> The standard pattern for type-erasure in Swift looks like this:
>>
>> protocol Farm {
>>     associatedtype Produce
>>     func grow() -> Produce
>> }
>>
>> private class _AnyFarmBase<T> : Farm {
>>     func grow() -> T { fatalError() }
>> }
>>
>> private final class _AnyFarmBox<U: Farm>: _AnyFarmBase<U.Produce> {
>>     var farm: U
>>     init(_ x: U) { farm = x }
>>     override func grow() -> U.Produce {
>>         return farm.grow()
>>     }
>> }
>>
>> public final class AnyFarm<V> : Farm {
>>     private let wrapped: _AnyFarmBase<V>
>>     func grow() -> V { return wrapped.grow() }
>>     init<W: Farm> (_ x: W) where W.Produce == V {
>>         wrapped = _AnyFarmBox(x)
>>     }
>> }
>>
>>
>> There is one little hiccough when you need an initializer in the abstract base 
>> class, which you can read about here 
>> <https://www.bignerdranch.com/blog/breaking-down-type-erasures-in-swift/> among 
>> other places.
>>
>> Hope that helps,
>>
>> Nevin
>>
>>
>> On Sun, Jul 16, 2017 at 12:32 AM, Glen Huang via swift-users <swift-users at swift.org 
>> <mailto:swift-users at swift.org>> wrote:
>>
>>     This sounds like the right approach!
>>
>>     However, as I experimented with AnyHashable more, I found out that after
>>     converting a concrete type to it, I could still convert back using “as”:
>>
>>     AnyHashable(Foo()) as! Foo
>>
>>     I guess that’s not the case with AnyNamed? I tried to imitate AnyHashable:
>>
>>     struct AnyNamed: Named {
>>         let base: Any
>>         init<T: Named>(_ value: T) {
>>             base = value
>>         }
>>
>>         var name: String {
>>             // How do I convert `base` to `Named` here?
>>         }
>>     }
>>
>>     But I have no idea what to put in `var name: String`. Also, even if we managed
>>     to come up with a solution, would it magically allow direct casting with “as”?
>>     Does the complier do something special for AnyHashable?
>>
>>
>>     > On 16 Jul 2017, at 12:58 AM, Ole Begemann <ole at oleb.net
>>     <mailto:ole at oleb.net>> wrote:
>>     >
>>     > One way to do this in Swift is a method called type erasure.
>>     >
>>     > Type erasure means you create a new type that wraps any value whose concrete
>>     type you want to erase. This new type also conforms to the protocol. By
>>     convention the type is named Any... (compare AnyIterator and AnySequence in the
>>     standard library, which do the same thing).
>>     >
>>     > struct AnyNamed: Named {
>>     >    private let _name: () -> String
>>     >
>>     >    init<T: Named>(_ value: T) {
>>     >        _name = { value.name <http://value.name/> }
>>     >    }
>>     >
>>     >    var name: String {
>>     >        return _name()
>>     >    }
>>     > }
>>     >
>>     > AnyNamed is initialized with a generic value T: Named. Notice that the
>>     initializer is generic, but the type itself isn't. Because AnyNamed can't store
>>     value: T directly (then it would have to be generic over T), we create a
>>     closure over value.name <http://value.name/> and store that instead.
>>     >
>>     > Now we can create a Set<AnyNamed> and, because AnyNamed conforms to Named,
>>     treat the set's elements as values conforming to Named:
>>     >
>>     > var set = Set<AnyNamed>()
>>     > set.insert(AnyNamed(Foo()))
>>     > set.insert(AnyNamed(Bar()))
>>     >
>>     > for element in set {
>>     >    print(element.name <http://element.name/>)
>>     >    print(element.hashValue)
>>     > }
>>     >
>>     >
>>     > On 11.07.2017 12:10, Glen Huang via swift-users wrote:
>>     >> Hi,
>>     >>
>>     >> I want to store some heterogeneous items all conform to a protocol inside a
>>     set, is it something possible to do in swift?
>>     >>
>>     >> I tried this example:
>>     >>
>>     >> ```
>>     >> protocol Named: Hashable {
>>     >>    var name: String { get }
>>     >> }
>>     >>
>>     >> extension Named {
>>     >>    var hashValue: Int {
>>     >>        return name.hashValue
>>     >>    }
>>     >>
>>     >>    static func ==(lhs: Self, rhs: Self) -> Bool {
>>     >>        return lhs.name <http://lhs.name/> == rhs.name <http://rhs.name/>
>>     >>    }
>>     >> }
>>     >>
>>     >> struct Foo: Named {
>>     >>    var name = "foo"
>>     >> }
>>     >>
>>     >> struct Bar: Named {
>>     >>    var name = "bar"
>>     >> }
>>     >>
>>     >> var item = Set<Named>()
>>     >> item.insert(Foo())
>>     >> item.insert(Bar())
>>     >> ```
>>     >>
>>     >> But it failed at `Set<Named>()` where it complained "Using 'Named' as a
>>     concrete type conforming to protocol 'Hashable' is not supported”.
>>     >>
>>     >> After watching the WWDC session "Protocol-Oriented Programming in Swift” by
>>     Dave Abrahams, I try to use protocols whenever possible. But I can’t seem to
>>     overcome this barrier. Set.Element must confirm to Hashable, which inherits
>>     from Equatable, which has self requirement, which ultimately means that
>>     Set.Element all must be of the same type. So it seems it’s impossible to have
>>     heterogeneous items using protocol. Is that the case?
>>     >>
>>     >> My use case is this:
>>     >>
>>     >> I have an object that can contain two sets of other objects:
>>     >>
>>     >> ```
>>     >> class Parent {
>>     >>    var foos: Set<Foo>
>>     >>    var bars: Set<Bar>
>>     >> }
>>     >> ```
>>     >>
>>     >> I want to define a computed property “all” that is the union of the two
>>     sets. Foo and Bar conform to the same protocol. I wonder what return type I
>>     should use for the union? Do I have to go back to OOP and define a super class
>>     for Foo and Bar?
>>     >>
>>     >> Thanks.
>>     >> _______________________________________________
>>     >> swift-users mailing list
>>     >> swift-users at swift.org <mailto:swift-users at swift.org>
>>     >> https://lists.swift.org/mailman/listinfo/swift-users
>>     <https://lists.swift.org/mailman/listinfo/swift-users>
>>     >
>>     >
>>
>>     _______________________________________________
>>     swift-users mailing list
>>     swift-users at swift.org <mailto:swift-users at swift.org>
>>     https://lists.swift.org/mailman/listinfo/swift-users
>>     <https://lists.swift.org/mailman/listinfo/swift-users>
>>
>>
> 
> 
> 
> _______________________________________________
> swift-users mailing list
> swift-users at swift.org
> https://lists.swift.org/mailman/listinfo/swift-users
> 


More information about the swift-users mailing list