I was thinking of unbound methods, not key paths. But…
…you make an excellent point! At first blush, key paths seem better suited for capturing mutating methods than unbound method references, since they solve the problem of what gets mutated:
let a = Foo().mutatingMethod // if we use a, how do we get the mutated Foo?
let b = \Foo.mutatingMethod // if we use b, we have to provide the Foo; problem solved
Yeah, I realized after I posted that "existential" is the wrong concept here: an existential always has a concrete type at runtime, whereas here T
remains free until called. (Also, existentials would be “any” and not “some” in my fake syntax examples. )
Out of my league too, but I think you're right about it being rank-N types. My fake syntax should have been:
forall <T> KeyPath<Foo, (T) -> [T]>
…or perhaps a more Swift-like term than “forall.” (It’s ironic that Swift, so ML-like in so many ways, doesn’t even have rank 1 function types like 'a → 'a!)
In any case, since Foo().bar
with no further context wouldn’t be allowed in my example, so it follows that \Foo.bar
shouldn’t be either — and that rules out the problem altogether.
It's worth noting that Swift does currently allow Foo().bar
in that example if there's enough context to make the type specific:
Foo().bar as (Int) -> [Int] // these compile today
Foo().bar as (String) -> [String]
…so should these be allowed too?
\Foo.bar as KeyPath<Foo, (Int) -> [Int]>
\Foo.bar as KeyPath<Foo, (String) -> [String]>
By contract, AFAICT my second example (\Sequence.joined
) would still be ruled out entirely in the Swift type world of today; there’s no way to sufficiently narrow the types at key path creation time if the root is Sequence
.