Hi! I recently got stumped by a compiler limitation while trying to implement a seemingly-reasonable protocol, and I'm curious if anyone has some insight on how to properly approach the problem.
The idea was to describe a general tree structure like this:
protocol TreeNode: AnyObject {
var parent: Self? { get }
var children: [Self] { get }
}
This was motivated by code reuse for UI development on Apple platforms, so I'll be using that as a concrete example, but this isn't inherently specific to Apple platforms.
Apple's frameworks make heavy use of this kind of tree structure. Views, view controllers, and scene kit nodes are examples of common objects that could theoretically adopt this protocol. Then, generic algorithms like searching could be implemented once for all of these types rather than reimplementing them over and over. But it turns out that this protocol can't be adopted by a subclassable type. When you try to add a conformance to UIView for example, you get this error:
Protocol 'TreeNode' requirement 'parent' cannot be satisfied by a non-final class ('UIView') because it uses 'Self' in a non-parameter, non-result type position
Please correct me if I get anything wrong, but my understanding of this limitation is that it prevents a subclass from breaking the superclass's protocol conformance. If UIView conforms to the protocol, then Self resolves to UIView. A subclass of UIView (say, UILabel) would inherit this conformance, but it would be invalid to do so. If UILabel conforms to TreeNode, then Self must refer to UILabel and not UIView, but the protocol was already implemented in terms of UIView.
That makes sense, but on the other hand it also seems reasonable in this case to want to say that Self continues to refer to the original conforming type in the subclass. A UILabel's superview and subviews are UIViews, not UILabels, but it seems that there's no way to use a protocol to express that generically.
So I have two questions:
Are there other viable approaches to achieve a similar end result?
Has there been any discussion about changes that could make this approach viable in the future?
I thought of that as well. Though it seems we want child->parent to point back to self, so it's probably nicer to put them in protocol requirement itself.
Constraining Child == Self on the extension pushes out the original error to the call site:
Referencing instance method 'contains' on 'TreeNode' requires the types 'UILabel' and 'UIView' be equivalent
But the last option seems to be a suitable workaround! The main drawback is that any TreeNode extension that needs Self to be homogenous with Base needs to force-cast to make that happen. Which makes the implementations more complicated and means that if you don't fulfill the implicit requirement of Base == Self, you'll crash at runtime. But I can deal with that for my use case. Thanks for your help @Lantua and @griotspeak
It seems like this might be the best that can be done currently in Swift, but if anyone has any insight I'm curious if there are any potential language changes that would support this more properly.
I'm not very knowledgeable on the subject, but would it not be possible for Swift to support some variant of Self in a protocol that could mean "subtype of Self" rather than "exactly Self"?
What about "supertype of Self" since that's what cause this problem.
because to UILabel think Child is UILabel as per the condition Child == Self.
We could essentially add Child : Self or Self : Child, but the intricacy needs to be taught out. What if Self is a final class, or doesn't have super class?