Swift - Generic Class Inheritence

generics
reference
inheritance

(Jak Jonnalagadda) #1
class Inheriting<Key: Equatable> {
  var key: Key?
}

class InheritenceTesting<Key: Comparable, Value>: Inheriting {
  var key: Key
  var values: [Value]?

  init (key: Key) {
    self.key = key
  }
}

Output Error:
error: reference to generic type 'Inheriting' requires arguments in <...> class InheritenceTesting<Key: Comparable, Value>: Inheriting { ^ <<#Key: Equatable#>>

As you can see from the above implementation and errors, It is not supported in Swift. I think in general, swift Generic inheritence is not flexible or strong. If we can support Generic inheritance like above then we can write subclasses to avoid a lot bioler plate code and also we can chenace optional stored property to required stored property to avoid a lot of optional chaining.

Please share your thoughts.


(Jeremy David Giesbrecht) #2

That works just fine already. You just have two naming clashes in your example which confuse the compiler:

  1. You have reused the generic type identifier Key without specifying the relationship between the two types, so the compiler cannot know which you are trying to refer to where.
  2. You have reused the property identifier key with conflicting optionality, so the compiler cannot tell whether it is supposed to be an override or not.

This code works:

class Inheriting<SuperclassKey: Equatable> {
    var optionalKey: SuperclassKey?
}

class InheritenceTesting<SubclassKey: Comparable, Value>: Inheriting<SubclassKey> {
    var nonOptionalKey: SubclassKey
    var values: [Value]?
    
    init (key: SubclassKey) {
        self .nonOptionalKey = key
    }
}

print(InheritenceTesting<String, Bool>(key: ""))

(Jak Jonnalagadda) #3

Sorry. I don't think I was clear before.

What is the exact risk in overriding optional stored property with non-optional stored property in a subclass expecially when we are using Generics? Because compiler converts generic notations with exact implementation to optimize it during the compile time and it also know memory information required for these stored properties during the compile time.


#4
class Foo {
    var key: Int?
}
class Bar: Foo {
    override var key: Int
}

let x: Foo = Bar()
x.key = nil // uh oh. We just assigned nil to nonoptional property!

(Jak Jonnalagadda) #5

Compiler should be able the catch that just like the way it catches regular non-optional variable.

class Testing {
var key: Int
init(key: Int) { self.key = key }
}
let t: Testing = Testing(key: 10)
t.key = nil //Compiler Error.

And in the below example. You wont be able to access super Foo's key because its already overridden in Bar. So Key stored property will automatically become non-optional on Bar's instances.

class Foo {
var key: Int?
}
class Bar: Foo {
var key: Int
}
let x: Foo = Bar()
x.key = nil // Compiler Error.


#6

Unfortunately compiler cannot always know if Foo is also Bar at compile time

class Foo {
    var key: Int?
}
class Bar: Foo {
    override var key: Int
}
func f(y: Foo) {
    y.key = nil // should this be compile error? Somebody may or may not pass instance of Bar here. 
}

(Rod Brown) #7

I agree, but going even further... let’s imagine for a second that the compiler could notice there was a subclass with that override, and then throw a compiler error. It would make it extremely hard to reason about your code when someone used some property and then a workmate subclasses the class in a completely different area of the codebase, and all of a sudden all your methods stop working.

Inference and compiler “smarts” should only be a certain level of smart. If they’re not, they start throwing warnings in extremely detached elements of your code.

That’s why we have language paradigms like Optional - so it’s not on the compiler to be ultra smart and warn only when you don’t unwrap and it will definitely be nil. Instead, it makes you handle the case even if it may not ever be nil. That isn’t a bug. It makes your code more robust in that it can handle the potential cases specified by the type definition, even if they’re not ever used because your optional is always non-null for your specific subclass.

When it comes to subclassing and changing a definition, the key guiding principle is Substitutability. Is your class substitutable with its superclass? No, because you can set nil on the superclass, and you can’t on the subclass. See also about the Liskov Subtitution Principle.


(Jak Jonnalagadda) #8

I was thinking in case of only Generic because compiler replaces generic type with actual classes and structs during the compile time but it makes sense. Thanks!


(John McCall) #9

Jak, I edited your post to use code quotes, which is much easier and produces better results than trying to manually format code.

There is no generics restriction here and no lookup conflict about Key. The compiler message is pretty clear about what you're doing wrong: you've named a generic superclass without any generic arguments, and you need to give it those arguments, i.e. Inheriting<Key>, not just Inheriting.


(Jean-Daniel) #10

AFAIK, the compiler may replace generic type but this is an optimisation and not a guarantee.