swiftenumsprotocolscode-duplication

Is there a way to get generic constraints take in an enum in swift? Or is there a better way to reduce code?


What I want to do is somewhat complicated, pardon all the variable names.

Currently I have been to doing something like

enum Foo {
    enum Bar {
        case prop11
        case prop12
        case prop13
        // etc
    }

    enum Baz {
        case prop21
        case prop22
        case prop23
        // etc
    }
}

protocol Fum1 {
    var zot: Foo.Bar { get set }
}

protocol Fum2 {
    var zot: Foo.Baz { get set }
}

struct Grunt1: Fum1 {
    // code
}

struct Grunt2: Fum2 {
    // code
}

This is causing me to duplicate a LOT of code though. Because the same functions can (for the most part) be run on either Grunt1 or Grunt2, and I keep having to specify with something like this

protocol Bletch {
    func doSomething(to grunt: Grunt1)
    func doSomething(to grunt: Grunt2)
}

But the implementations are the same for both functions. This is quite frustrating. I'd like to do something like

protocol Fum {
    associatedType Thud

    var zot: Thud { get set }
}

struct Grunt<T: Foo>: Fum {
    typealias Thud = T
}

This would then let me do something like

let grunt1 = Grunt<Foo.Bar>(zot: .prop11) //Autocompletes to .prop11, .prop12, .prop13

let grunt2 = Grunt<Foo.Baz>(zot: .prop23) //Autocompletes to .prop23, .prop22, .prop23

Then I could do something like

protocol Bletch {
    func doSomething(to grunt: Fum) // or (any Fum), whichever doesn't throw errors.
}

But this doesn't work, Is there a different way of getting the result I'm after? I'm just trying to reduce duplicate code.


Solution

  • Most of what you describe already works except <T: Foo> since Foo is not a protocol.

    Instead of making Foo.Bar and Foo.Baz nested types, you can make Foo a protocol and let both Bar and Baz adopt the protocol. (why are they nested types anyways?):

    protocol Foo {}
    
    enum Bar : Foo { /* cases */ }
    enum Baz : Foo { /* cases */ }
    

    If that's not an option, you can also extend Foo.Bar and Foo.Baz and let them both adopt to a common protocol:

    protocol FooProtocol {}
    
    extension Foo.Bar : FooProtocol {}
    extension Foo.Baz : FooProtocol {}
    

    Now you just need to constraint your generic T on Grunt to be a FooProtocol instead of Foo:

    struct Grunt<T: FooProtocol>: Fum {
        typealias Thud = T
        var zot: Thud
    }