Key Paths as function: Why doesn't this code work?

SE-0249 introduced key path literal as function. Following code works well.

let count: (String) -> Int = \String.count
count("Hey") // 3

However, using this feature directly is not possible.

\String.count("Hey")
error: repl.swift:3:9: error: invalid component of Swift key path
\String.count("Hey")
        ^

error: repl.swift:3:1: error: key path must have at least one component
\String.count("Hey")
^

To treat \String.count as separate identifier, it still doesn't work.

(\String.count)("Hey")
error: repl.swift:3:2: error: cannot call value of non-function type 'KeyPath<String, Int>'
(\String.count)("Hey")
 ^

Why?

The proposal mentions that the compiler will prefer KeyPaths (or some subtype) over functions.

When inferring the type of a key path literal expression like \Root.value , the type checker will prefer KeyPath<Root, Value> or one of its subtypes, but will also allow (Root) -> Value . If it chooses (Root) -> Value , the compiler will generate a closure with semantics equivalent to capturing the key path and applying it to the Root argument

I think (I'm not an expert by any means) it would be difficult for the the compiler to know when to convert key paths into functions without any aide from us.

3 Likes

Umm...
I thought (\String.count)("Hey") is enough implying that literal should be treated as function, but it cannot be helped.

1 Like
(\String.count as (String) -> Int)("Hey")

works, so I think it should compile. There's no other built-in possibility.

You can make it compile by adding your own KeyPath.callAsFunction, but it would be redundant, given the "Hey"[keyPath: \.count] form.

1 Like

As Jessy mentions, you could make this compile by adding:

extension KeyPath {
    func callAsFunction(_ root: Root) -> Value {
        root[keyPath: self]
    }
}

And as the very last sentence of SE-0249 says: " Accepting key paths directly would also be more limiting and prevent exploring the future directions of Callable or ExpressibleByKeyPathLiteral protocols". ("Callable" is what eventually became callAsFunction.) So the proposal is explicitly excluding this from working, because it would conflict with callAsFunction.

2 Likes
Terms of Service

Privacy Policy

Cookie Policy