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

Nevin Brackett-Rozinsky nevin.brackettrozinsky at gmail.com
Sun Jul 16 15:02:54 CDT 2017


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> 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> 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 }
> >    }
> >
> >    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 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)
> >    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 == 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
> >> 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
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-users/attachments/20170716/6e6ca664/attachment.html>


More information about the swift-users mailing list