Boilerplate code in enum wrapper

Hi, I have a few types which conform to a protocol and I'd like to put values of those types in a single array. Because the protocol inherits from Equtable and Swift doesn't support generalized existential, it's impossible to define an array of that protocol type. After researching on the net, I find the two typical solutions are using type eraser or enum wrapper.

Type eraser is not applicable in my case, because I'd like to keep the original values, which are not saved at any other places. From my understanding, if I convert the original values to type erased values and save the latter in the array, there would be no way to get back the original values.

So I tried the enum wrapper approach. It works, but it can easily lead to boilerplate code. See the following code.

 1	protocol Foo: Equatable {
 2	    var foo: Int { get }
 3	}

 4	struct A: Foo {
 5	    var foo: Int
 6	    var a: Int
 7	}

 8	struct B: Foo {
 9	    var foo: Int
10	    var b: Int
11	}

12	enum FooWrapper: Foo {
13	    case a(_ value: A)
14	    case b(_ value: B)

15	    var foo: Int {
16	        switch self {
17	        case .a(let value):
18	            return value.foo
19	        case .b(let value):
20	            return value.foo
21	        }
22	    }
23	}

24	var store: [FooWrapper]

The issue is, if protocol Foo has, say, ten properties. I need to repeat line 16-21 ten times, one for each property. I wonder if there is a better way to deal with it?

I tried a few approaches, but none of them worked. One of the most promising approach (in my opinion) is like the following:

enum FooWrapper: Foo {
    case a(_ value: A)
    case b(_ value: B)

    var foo: Int {
        value.foo
    }

    var value: Foo {  // Error!
        switch self {
        case .a(let value):
            return value
        case .b(let value):
            return value
        }
    }
}

It fails to compile due to the same reason that we can't use Foo as array type. It doesn't work either if I change the return type to some Foo, because the value property doesn't return a fixed concrete type.

I also considered using dynamic member lookup feature, but find they're not helpful in this case.

I think there is one way that might work. It's to let the value property return a type erased value, like the following:

enum FooWrapper: Foo {
    case a(_ value: A)
    case b(_ value: B)

    var foo: Int {
        value.foo
    }

    var value: AnyFoo {
        switch self {
        case .a(let value):
            return AnyFoo(value)
        case .b(let value):
            return AnyFoo(value)
        }
    }
}

I don't like this approach, however, because it creates a type erased value every time the value is accessed. These values are the main entities in my app and they are accessed very frequently. Although it's unlikely to cause any real performance issue, it doesn't seem good to me (please correct me if I'm wrong).

From what I read on the net, enum wrapper is a frequently used approach for heterogeneous array in Swift. I wonder how you guys do it? Thanks.

1 Like

Do you want to keep properties A.a and B.b in your array? Or is it sufficient to keep just Foo.foo as values in an array?

You can do:

var foo: Int {
    switch self {
      case .a(let value as Foo), .b(let value as Foo):
        return value.foo
    }
  }

which takes a little less code. It won't compile right now because of the : Equatable constraint on Foo but in the next version of Swift it will (thanks to SE-0309) along with some other versions like the array example you gave above.

2 Likes

Thanks for the information and the code improvement!

1 Like

Yes. As Suyash Srijan pointed out, the issue will be gone in the next version of the Swift. So I'll just use the naive approach for now.

Terms of Service

Privacy Policy

Cookie Policy