Implicit member expressions and dynamic member lookup

Hey everyone,

One of the main selling points of the scoped functions pitch is, that it would enable this neat syntax which could be used e.g. for the building of comparisons for database queries:

I'm quite sceptical about the approach to achieve this syntax, as it would also need autoclosures with an argument.
However, when this syntax was proposed, I immediately thought that it should be possible in current Swift (namely Swift 5.4) already, if we used dynamic member lookup in combination with the new implicit member chains.

So I built this little example to see if it really works:

@dynamicMemberLookup
struct Comparison<T, U> where U: Comparable {
    private let lhs: KeyPath<T, U>
    private let rhs: U!
    private let op: ((U, U) -> Bool)!
    // op could also be an enum or something like that, which could be converted into an SQL query comparison...

    static subscript(dynamicMember keyPath: KeyPath<T, U>) -> Comparison<T, U> {
        return .init(lhs: keyPath, rhs: nil, op: nil)
    }

    static func ==(lhs: Self, rhs: U) -> Self {
        .init(lhs: lhs.lhs, rhs: rhs, op: ==)
    }

    func apply(to value: T) -> Bool {
        self.op(value[keyPath: self.lhs], self.rhs)
    }
}

struct Galaxy {
    let name: String
    let numberOfStars: Int

    static func getAll() -> [Galaxy] {
        // ...
    }

    static func filter<T>(_ isIncluded: Comparison<Galaxy, T>) -> [Galaxy] {
        let galaxies = getAll()
        return galaxies.filter(isIncluded.apply(to:))
    }
}

// Works
let comparison1 = Comparison<Galaxy, String>.name == "Milky Way" 

// Doesn't work: Type 'Comparison<Galaxy, String>' has no member 'name'
let comparison2: Comparison<Galaxy, String> = .name == "Milky Way" 


// Works
let filteredGalaxies = Galaxy.filter(Comparison.name == "Milky Way")

// Doesn't work: Type 'Comparison<Galaxy, String>' has no member 'name'
let filteredGalaxies2 = Galaxy.filter(.name == "Milky Way")

As already apparent from my commentary, this approach does unfortunately not work. The cause of that seems to be, that implicit member expressions don't currently follow dynamic member lookup.
To rule out that my errors have any different source than the dynamic member lookup, I provided this extension, which indeed resolves the errors:

extension Comparison where T == Galaxy {
    static var name: Comparison<Galaxy, String> {
        .init(lhs: \.name, rhs: nil, op: nil)
    }

    static var numberOfStars: Comparison<Galaxy, Int> {
        .init(lhs: \.numberOfStars, rhs: nil, op: nil)
    }
}

So dynamic member lookup really is the cause of the errors here.
I can understand that implicit member expressions do not work in the case of normal String-keyed dynamic member lookup, but for the type-safe KeyPath-keyed lookup there should not be any problem with allowing it in implicit member expressions.
Should I make this into a pitch? Currently I have very little time to write a proposal, let alone to develop a PR for it. But I can imagine that this feature could bring some nice possibilities for APIs and also that it fits nicely in the direction of Swift.
Is there maybe someone else who has the same feeling about it and could support me?

Have a nice day!
- Josef Zoller

5 Likes
Terms of Service

Privacy Policy

Cookie Policy