In most parts of Swift, instance methods are treated like properties. However, if you try to reference an instance method using a key path, you get a compiler error. Why is this the case? I assume there is a reason, but I cannot find it. It would be extremely useful in all the same places that other key paths are.
Note that I am not asking about calling instance methods inside a key path. I want to know why this works:
let instance = 100
let method = instance.isMultiple
method(10) // true
method(11) // false
while this doesn’t:
let instance = 100
let method = instance[keyPath: \.isMultiple] // Key path cannot refer to instance method 'isMultiple(of:)'
method(10)
method(11)
I was wondering about that too. I firmly believe that key paths referring to instance methods would be a really powerful feature and seems only natural to be added to the language. I thought that the following would work normally:
struct Dog: Animal {
var age: Int
func greet() { /*...*/ }
func bark() { /*...*/ }
}
@dynamicMemberLookup
struct AnimalWrapper<Wrapped: Animal> {
var wrapped: Wrapped
subscript<Value>(dynamicMember keyPath: KeyPath<Wrapped, Value>) -> Value {
/* ... */
}
}
let dog = Dog(age: 13)
let animal = AnimalWrapper(wrapped: dog)
animal.age // Int
animal.greet // () -> ()
This doesn't work because keyPaths can't reference instance methods, but getting the bark method as a closure is allowed:
That’s precisely the sort of thing I want to do. Dynamic member lookup with key paths would be perfect for wrapper types, were it not for this limitation.
If you assign an instance method to a property, that property can be referenced through key paths. I know that a function’s type does not include parameter labels: maybe it’s that sort of label erasure that requires special support?
I'm not at all familiar with compiler architecture and the process of contribution. Do you happen to know what would need to be done in order to implement this feature?
This snippet explains the limitation of dynamic member lookup, not KeyPath. This is not an explanation of why KeyPaths don't supports method, but a consequence.
There's no fundamental reason KeyPaths can't support instance methods. It would be a reasonable thing to add to the language. The implementation would be more or less identical to how read-only subscripts are handled.
Judging by the last commit on the branch, his implementation wasn’t compatible with Swift 5.0 (the contemporary version). It worked on the Swift 5.1 beta, though.
This would be quite fantastic to get going, and we've been dreaming up some use-cases for this a while ago.
What I would be most interested in is ensuring that it is also possible to capture arguments in a KeyPath (slowly leading towards not only wrapping properties, but any function really via a kind of "function wrapper" which would kind of look the same -- get a KeyPath representing the invocation)...
Assuming you mean argument labels, that seems way out of scope for this. I agree that it would be helpful, but that isn’t actually part of a function’s type right now.
This innocent code creates a retain cycle under Siesta’s memory rules. No need to understand the context or fiddly details here; the fundamental problem is that self.fooChanged retains self, and the way Siesta works, we don’t want that.
Folks have suggested weak closures and/or weak method references (e.g. here, here), but another alternative that requires no new syntax and little new discussion — just the proposal at hand here — would be to alter Siesta’s observer API to use a keypath:
If methods in key paths work like read-only subscripts, all arguments would need to be bound. Only self Is not bound. I looked at the siesta docs and it looks like the callback accepts two arguments that must be provided by the caller and neither appear to be the observer’s self which is what you would expect to pass to a key path.
It would be great if we could extend the read only subscript model somehow to support unbound methods where parameters are unbound in addition to self, or perhaps even allowing arbitrary “holes” in the path (including subscript parameters). This works with key path syntax. It might be possible to make it work with the KeyPath type as well by making Root a tuple, but I’m not sure that’s a good idea.