swiftgenericsvariadicgeneric-type-parametersswift-keypath

Type Parameter Packs: how to return a tuple instead of an array?


To begin with I have this code, which works totally fine:

protocol ComponentAccessible {}

extension ComponentAccessible { 
    func components<each T>(_ keyPaths: repeat KeyPath<Self, each T>) -> (repeat each T) {
        { (repeat self[keyPath: each keyPaths]) } 
    } 
}

// MARK: - Usage 

extension URL: ComponentAccessible {}
url.components(\.scheme, \.host, \.path)

But now I want to try a slightly different approach (making use of PartialKeyPath):

extension ComponentAccessible {
    func components<each T>(_ keyPaths: PartialKeyPath<Self>...) -> (repeat each T) {
        keyPaths.map { self[keyPath: $0] }
    }
}

This doesn't compile, because I'm returning an array, not a tuple.


Solution

  • Firstly, this is not type-safe. PartialKeyPath doesn't know the type of the value of the key path, only the type of the root. So to be able to return (repeat each T), you need to cast.

    Secondly, the type of keyPaths, PartialKeyPath<Self>..., is not a pack expansion. It needs to be a pack expansion for you to be able to return a tuple with a matching number of elements.

    So you need the type pack each T to appear somewhere in the type of keyPaths, so that you can write a pack expansion for the type of keyPaths, i.e. the caller has to pass in the types they want one way or another.

    At this point, you should realise that you should just keep using KeyPath<Root, Value>, because you can directly use each T there. If you want a "syntax exercise", you can take pairs of a PartialKeyPath<Self> and a metatype.

    extension ComponentAccessible {
        func components<each T>(keyPaths: repeat (PartialKeyPath<Self>, (each T).Type)) -> (repeat each T) {
            (repeat self[keyPath: (each keyPaths).0] as! each T)
        }
    }
    

    You also cannot return a tuple of Anys. While you can "trick" the compiler into thinking you are using a type pack by writing a type alias, the compiler will still "see what you are doing" later down the line. These don't work:

    typealias A<X> = Any
    typealias PKP<X, Y> = PartialKeyPath<X>
    // now you can write (repeat A<each T>) to mean a variadic tuple of Anys, but...
    
    extension ComponentAccessible {
        // the compiler can see that you are not using T anywhere,
        // and no, you cannot add a dummy metatype parameter with a default value
        // parameter pack parameters cannot have default values
        func components<each T>(keyPaths: repeat PKP<Self, each T>) -> (repeat each A<each T>) {
            (repeat self[keyPath: each keyPaths])
        }
    
        // the compiler cannot form the constraint that keyPaths should have the same shape as the return type
        func components<each T>(keyPaths: repeat PKP<Self, each T>) -> (repeat each T) {
            (repeat self[keyPath: each keyPaths] as! each T)
        }
    }