Imagine I have a struct AStruct:
struct AStruct {
let name: String
let value: Int
}
And imagine I have an array of AStruct
s:
let array: [AStruct] = [
AStruct(name: "John", value: 1),
AStruct(name: "Bob", value: 23),
AStruct(name: "Carol", value: 17),
AStruct(name: "Ted", value: 9),
AStruct(name: "Alice", value: 13),
AStruct(name: "Digby", value: 4)
]
Now imagine I want to use a map()
to create an array of integers out of my array of AStruct
s.
This code works:
let values = array.map { $0.value }
But if I try to use Swift key path syntax I get errors no matter what I try. Example:
let values = array.map { \.value }
I get an error:
Cannot infer key path type from context; consider explicitly specifying a root type.
So how do I that?
I can get it to work if I give the input a name and type:
let values = array.map { (aVal: AStruct) -> Int in
return aVal.value
}
But that is distinctly not key path syntax.
CodePudding user response:
SE-0249: Key Path Expressions as Functions is the relevant Swift Evolution proposal for this feature, and from that proposal:
As implemented in apple/swift#19448, occurrences of
\Root.value
are implicitly converted to key path applications of{ $0[keyPath: \Root.value] }
wherever(Root) -> Value
functions are expected. For example:users.map(\.email)
Is equivalent to:
users.map { $0[keyPath: \User.email] }
<snip>
When inferring the type of a key path literal expression like
\Root.value
, the type checker will preferKeyPath<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. For example:// You write this: let f: (User) -> String = \User.email // The compiler generates something like this: let f: (User) -> String = { kp in { root in root[keyPath: kp] } }(\User.email)
In other words: the compiler recognizes a keypath expression when passed in as a direct argument to where a function is expected, and injects a conversion from the keypath to a generated closure. When you write
array.map { \.value }
you are passing in a closure whose body evaluates to a key path, and because the key path is then seemingly arbitrary to the compiler here (e.g., it has no context for what type \.value
is rooted off of), you get the error.
Also important to note is that:
The implementation is limited to key path literal expressions (for now), which means the following is not allowed:
let kp = \User.email // KeyPath<User, String> users.map(kp)
I don't recall whether this restriction has been lifted at this point, but if you find yourself in a situation where you have a keypath you need to apply but automatic conversion won't work, you'll need to pass the keypath into a closure manually, and key off of that keypath directly.
CodePudding user response:
SE-0249 allows for key paths to be treated as closures. map
takes an "(Element) throws -> T
"—for example, in your case, this one:
{ $0.value } as (AStruct) -> _
But a key path is not a closure. So you can't treat it as one…
array.map { (\.value)($0) }
…unless you explicitly type it:
array.map { (\.value as (_) -> _)($0) }
But you would never do that. The original
array.map { $0.value }
syntax is better. However, the most common way to cast the key path to a closure is better-looking.
array.map(\.value)