Today a key path may only reference properties and subscripts:
\Array<String>.[0].count
But cannot reference method calls:
\Array<String>.[0].lowercased().count
// ^
// error: Invalid component of Swift key path
Since Swift already supports subscripts, how hard can it be to implement support for method calls in key paths? Sure, subscripts are different from regular methods, but are they different enough to be an implementation problem?
I imagine it could open a lot of new possibilities. The first thing that comes to mind is allowing a type expose methods of other type. Let me clarify.
Today we can do this:
@dynamicMemberLookup
struct Transparent<Source> {
let source: Source
subscript<Destination>(dynamicMember keyPath: KeyPath<Source, Destination>) -> Destination {
return source[keyPath: keyPath]
}
}
let transparent = Transparent<String>(source: "hello")
print(transparent.capitalized) // prints "HELLO"
This serves the same purpose as the Deref
trait in Rust. However, the Deref
trait would enable calling the methods of type Source
on the Transparent
instance, which we can't do right now.
Now, some of you remember discussions about passing methods of classes in point-free style and the problems that it may hide: [Pitch] Introduction of `weak/unowned` closures
tl;dr: If we don't want to strongly capture self
when passing a method to a function that takes another function, we can't do it in point-free style, like so:
import Dispatch
class C {
func doStuffAsynchronously() {
// doStuf is passed in point-free style here
DispatchQueue.main.async(execute: doStuff) // self is captured by strong reference here!
}
func doStuff() {
// ...
}
}
Instead, we have to do this:
DispatchQueue.main.async { [weak self] in self?.doStuff() }
This is a lot less concise and I'm sure seems uglier to many of us.
There were proposals that tried to address this at the language level. Interestingly, allowing function calls in key paths could solve this at the library level in quite a general way. Imagine this little helper:
@dynamicMemberLookup
struct WeaklyCapture<T: AnyObject> {
private weak var object: T?
init(_ object: T) {
self.object = object
}
subscript<Destination>(dynamicMember keyPath: KeyPath<T, Destination>) -> Destination? {
return object?[keyPath: keyPath]
}
subscript(dynamicMember keyPath: KeyPath<T, Void>) -> Void {
object?[keyPath: keyPath]
}
}
Now we can use point-free style to pass methods around, and it reads nice:
DispatchQueue.main.async(execute: WeaklyCapture(self).doStuff)
The same could be done for unowned
. What's good about this is that we're not limited to capturing only self
, we can capture any variable with this solution.
I remember someone from the core team mentioning that function calls in key paths is something that we want to eventually support. What implementation challenges can one encounter should they try to implement this in the compiler and the standard library?