Determining if key-paths will access the same property

Hi,

I have encountered a problem while implementing pure Swift (manual) KVO.

Given two classes A and its subclass B with class A introducing property i the following is
false:

import Foundation

\B.i == \A.i

Both key-paths access the same property, though:

let b = B()

// Prints
//
// B.i:getter
// A.i:getter
b[ keyPath: \B.i ]

// Also prints
//
// B.i:getter
// A.i:getter
b[ keyPath: \A.i ]

with

class A 
{
	private var _i = 0
	
	var i: Int
	{
		get
		{
			print( "A.i:getter" )
			return _i
		}
		
		set
		{
			print( "A.i:setter" )
			_i = newValue
		}
	}
}

and

class B : A
{
	var j = "foo"
	
	override var i: Int
	{
		get
		{
			print( "B.i:getter" )
			return super.i
		}
		
		set
		{
			print( "B.i:setter" )
			super.i = newValue
		}
	}
}

So there should be all information available to answer the question if two key-paths will access the same property.

Would be nice if there's a function that tests if two key-paths access the same property, for example:

extension AnyKeyPath
{

	func accessesSameProperty( as other: AnyKeyPath ) -> Bool

}

Better yet, if Equatable for AnyKeyPath would be implemented to return if both of =='s operands will access the same property and the implementation of the Hashable protocol would produce the same hash for key-paths whose root types are related. A new function in AnyKeyPath could determine if two key-paths are identical as the == operator does now (e.g. identical(to:)).

Other use-cases could be ORM, mapping per-property configuration/state/flags, selective processing of bindings.

Of course, I always could inherit from NSObject (which would exclude Linux, though).

Any thoughts on this?

The biggest structural problem as I see it to doing something like this is that \A.i and \B.i only access the same property when in the context of an instance of B; that is, these two statements access different properties:

A()[keyPath: \A.i]
B()[keyPath: \B.i]

Thus, to implement this kind of comparison you'd have to also pass in some kind of type context, which necessarily means you can't use operators/Equatable conformance in any simple way - you would have to just have a function that could do this comparison, and handling all the cases with inheritance and dynamic dispatch information lookup would likely be a non-trivial task to implement.

Yes, this would require the operators to perform dynamic dispatch information lookup. Type context is only necessary as an entry point into the class hierarchy which would be traversed up to the first superclass that introduced the property.

Type context is only necessary as an entry point into the class hierarchy which would be traversed up to the first superclass that introduced the property.

My point is more that it would be incorrect to say that \A.i == \B.i in all contexts, given that they access different properties when used as subscripts to instances of different types. We might be able to say that \A.i == \B.i holds in the context of an instance of B where B: A, but to state that there is global equality between \A.i and \B.i under all circumstances (which the == operator semantically means) would be fundamentally not correct.

1 Like

How key-paths are currently implemented, both key-paths \A.i and \B.i access the same property but at the same time point to different implementations . When you create a key-path you are interested in accessing the property (that is, the most specialised version of it) and not in calling or identifying the implementation of a specific type of the property.

subscript(keyPath:) respects the polymorphism of classes and calls, depending on the instances's concrete type, the most specialised implementation. This is what you want when you access a property directly by invoking its setter/getter ( B().i = 11 ) or indirectly using a key-path ( B()[keyPath:\A.i] ). This means the implementation of subscript(keyPath:) sees in key-paths identifiers that specify which property to be accessed.

AnyKeyPath 's current implementation of the Equatable protocol has only little practical value. It returns if two key-paths have been created with the same types. This means that the key-paths from the perspective of the implementation of the Equatable protocol point to a specific implementation of a property's setter and getter.

That key-paths are interpreted to point to different things is an inconsistency. And I think, this should be resolved.

Changing the implementation of the Equatable protocol so that it matches that of the subscript is more reasonable than the other way around. The subscript takes the polymorphism of classes into account and behaves as a programmer expects. If the subscript would adopt the view of the implementation of the Equatable protocol on key-paths then the overridden versions of the setter and getter in B of the i property would not be called if accessed with \A.i which would render the subscript useless.

Terms of Service

Privacy Policy

Cookie Policy