Nick
Nick

Reputation: 4258

Map enum to KeyPath to access properties in a generic way

I am trying to map enum values to properties on a struct, so the properties can be accessed by enum, and the properties can be iterated and processed in a generic way. Here is a contrived example, which demonstrates the compiler errors I'm getting:

import UIKit

protocol AProtocol: Equatable {
    var someInstanceProperty: String { get }
}

extension Bool: AProtocol {
    var someInstanceProperty: String { "Bool" }
}
extension Int: AProtocol {
    var someInstanceProperty: String { "Int" }
}
extension String: AProtocol {
    var someInstanceProperty: String { "String" }
}

struct Values {
    var someBool = false
    var someInt = 0
    var someString = "hello"
    
    enum Properties {
        case someB, someI, someS
    }
    
    let map1: [Properties: PartialKeyPath<Values>] = [
        .someB: \Values.someBool,
        .someI: \Values.someInt,
        .someS: \Values.someString,
    ]
    
    let map2: [Properties: KeyPath<Values, AProtocol>] = [
        .someB: \Values.someBool, // Cannot convert value of type 'KeyPath<Values, Bool>' to expected dictionary value type 'KeyPath<Values, AProtocol>'
        .someI: \Values.someInt, // Cannot convert value of type 'KeyPath<Values, Int>' to expected dictionary value type 'KeyPath<Values, AProtocol>'
        .someS: \Values.someString, // Cannot convert value of type 'KeyPath<Values, String>' to expected dictionary value type 'KeyPath<Values, AProtocol>'
    ]
    
    func performSomeAction<T: AProtocol>(key: Properties, keyPath: KeyPath<Values, T>) {
        print("key \(key), value \(self[keyPath: keyPath].someInstanceProperty)")
    }
    
    func getAll1() {
        for (key, keyPath) in map1 {
            performSomeAction(key: key, keyPath: keyPath) // Cannot convert value of type 'PartialKeyPath<Values>' to expected argument type 'KeyPath<Values, T>'
        }
    }
    
    func getAll2() {
        for (key, keyPath) in map1 {
            print("key \(key), value \(self[keyPath: keyPath].someInstanceProperty)") // Value of type 'Any?' has no member 'someInstanceProperty'
        }
    }
    
    func getAll3() {
        for (key, keyPath) in map1 {
            print("key \(key), value \((self[keyPath: keyPath] as! AProtocol).someInstanceProperty)") // Protocol 'AProtocol' can only be used as a generic constraint because it has Self or associated type requirements
        }
    }
}

In map1, the value type is PartialKeyPath. I can't use KeyPath because the value types are different (even though they all conform to AProtocol), so I get a compiler error (see map2). The problem with map1 is that the values lose their type information. The three getAll functions demonstrate various approaches I've tried to overcome this, but haven't been successful. The closest I've got is getAll3, which works if I remove Equatable conformance from AProtocol. Unfortunately I need the values to be equatable – in my real code I'm comparing previously stored values with current property values. Is this possible?

Upvotes: 1

Views: 219

Answers (0)

Related Questions