This looks like it's just a hole in the keypath-to-function conversion. As you note, the KeyPath<T, U> to (T) -> U conversion is valid, as is the (T) -> U to (T) -> U? conversion.
The error we get here is identical to the error we get for an attempted KeyPath<T, U> to KeyPath<T, U?> conversion, so it seems like the target function type is being transformed into its corresponding keypath type to determine if the conversion is valid, which causes us to miss out on the potential function-function conversions that are allowed.
I agree that it probably should not compile if there are no optionals that require the use of compactMap as opposed to just map, I'm just generally a little confused about non-equivalence of these kinds of closures and the corresponding key paths. An adjacent case of unexpected non-equivalence:
let words = ["Hello", "world"]
let mapped1 = words.map { $0 }
print(mapped1) // works as expected
//⚠️ Doesn't work:
//let mapped2 = words.map(\.self)
//print(mapped2)
Actually, I think that should not work either: When there is no Optional involved, map is the right function to use; that it compiles is a side effect of the "T can be used like T?" convenience.
I disagree. Implicit conversion of T to T? is a very useful feature, in my opinion. This code compiles directly because of this implicit conversion, not as a side-effect thereof. If this is a side-effect, what is the intended purpose of this feature?
Is the compactMap(\.name) issue above also a bug? Should it be considered a bug? If so, is it already known, or should I report it? (I'm new to this if you can't tell. )
Right, I suppose my question is: Is this lack of support of the implicit conversion from KeyPath<T, U> to KeyPath<T, U?> an accidental omission that could/should be rectified in the future, or is it a conscious design-decision (and, in the latter case, what are the reasons)?
It's not immediately intuitive to me why this isn't supported when I read:
Swift has an implicit conversion from A to Optional<A>everywhere.
More specifically, that should read something like:
Swift has an implicit conversion from [values of type] A to [values of type] Optional<A>everywhere.
Other conversions of Optional in generic argument position (such as Array<T> to Array<T?>) are special-cased and can't (currently) be applied to arbitrary types in Swift.
IMO, even if we don't allow for the fully general KeyPath<T, U> to KeyPath<T, U?> conversion, we should allow for it in cases where the keypath is already being converted to a function. The KeyPath<T, U> to KeyPath<T, U?> to (T) -> U? chain may not work, but the KeyPath<T, U> to (T) -> U to (T) -> U? path is perfectly valid under the existing conversion rules, so I don't see a great reason why we shouldn't support it.
I'm not sure I'm seeing how this contradicts what @Jumhyn said before.
Elsewhere we can also pass a function (T) -> U where a function (T) -> U? is expected:
struct MyStruct<T, U> {
let doSomething: (T) -> U?
}
func doSomething(_ text: String) -> Int { 42 }
let myThing = MyStruct(doSomething: doSomething)
A key path resolves to a method signature, the compactMap function takes a closure of type (Element) -> T?, if you pass it a key path on the Element type (in your case a property getter) which returns T then it would be like trying to pass a closure specifically typed (Element) -> T and not (Element) -> T?
In my example above, MyStruct takes a method (T) -> U? and is perfectly fine with me passing it the function doSomething which is a (T) -> U (where T is String and U is Int) function and does not return an optional.
So shouldn't compactMap, which takes a closure of type (Element) -> T? then also take something that's equivalent to (Element) -> T?
Isn't this a similar situation to your let forced: MaybeDoIt = { Int($0)! } example?
I saw your example, and to me the complier should not let that happen, perhaps it is possible cause the function gets resolved to an auto-closure and inlined by the compiler just as { 42 }
In any event I think either bothpersons.compactMap { $0.name }andpersons.compactMap(\.name) should be valid or neither should be if name is non-optional.
And since I suspect many other things depend on that implicit conversion from type containingU to type containingU? the solution is probably to make both valid rather than the opposite.