why is explicitly annotating super call in inherit...
# getting-started
r
why is explicitly annotating super call in inheritance necessary?
Copy code
abstract class Base (x: Int, y: Char, z: String, q: Float)  {
    fun fn() {}
}

class Sub : Base {
         // ^ This type has a constructor, and thus must be initialized here
}
Why can't it just, inherit the constructor, since the sub class has not given an override? Like in other languages. This forces me to redundantly write the signature
Copy code
class Sub(x: Int, y: Char, z: String, q: Float) : Base(x,y,z,q) {
    
}
which sucks. My use case is a 4+ depth inheritance hierarchy, where the properties are all the same. so that is 4x redundancy.
b
class Sub(private val a: Int, x: Int, y: Char, ...)
The compiler shouldn't necessarily be making assumptions on what the super constructor's relationship to the sub-class constructor.
r
why not if the sub class does not specify a constructor..?
that is how all other methods are inherited
v
If it does not specify one explicitly, it has a no-arg one implicitly and that does not match the super constuctor, so you have to be explicit
👍 1
b
It does though, even if the syntax is hidden due to sugaring. ^
v
Also, constructors are never inherited
r
Why wouldn't it have the super constructor implicitly?
that sounds like a sensible design
v
If the superclass has a constructor taking X and the subclass has a constructor taking Y, you can only call the Y-constructor on subclass
r
yes that is if the subclass specifies it's own constructor
but if it never does that... why not just assume super constructor signature?
v
And what sensible design is, is a very subjective matter 😉
👍 1
But it does, it has a no-arg constructor
r
Yeah I am asking why the design is to assume a no-arg constructor, rather than the base super constructor
v
That's also consistent with what Java does for examlpe
👍 1
Well, why not?
r
because doing that results in this massive redundancy requirement I'm encountering
with seemingly no positive trade off
if sub classes would assume the constructor of their parent (given they have not provided their own constructor), then you could easily build inheritance hierarchies of arbitrary depth never having to repeat a constructor
v
In your case
Either design probably makes sense for some people and not for the others
If you really need to know why, you probably have to search whether a KEEP exists where this is a topic or ask the language designers otherwise
given they have not provided their own constructor
That's the point, they do have a constructor
r
explicitly provided their own constructor
I mean in any case in which the sub class does not differ in the properties of the base class constructor
seems like there is an advantage to assuming the parents ctor
which is just avoiding repetition
v
Again, search for a KEEP that explains it or ask the language designers. 🙂 I doubt anyone here can read their minds. Maybe they just adapted the sensible design of Java
r
yeah
v
> I mean in any case in which the sub class does not differ in the properties of the base class constructor Which are probably very few
r
@Vampire Sure but in those events, you already are having to define your own constructor. so the redundancy makes sense
It's like: Either you have identical properties, in which case redefining a constructor is redundant or you have different properties, so you already were going to have to explicitly implement your own constructor
so in either case, assuming the parent's constructor absent any explicit override is net advantageous
you don't lose anything but do gain the ease of avoiding redundancy
javascript is gross but that is how javascript handles constructors when you dont explciitly override them
v
@Vampire Sure but in those events, you already are having to define your own constructor. so the redundancy makes sense
No, I don't have to.
And I would never accept JS as "good example" for anything. :-D
r
Copy code
abstract class Base (val x: Int, val y: Char, private val z: String, private val q: Float)  {
    fun fn() {}
}
abstract class SubA(x: Int, y: Char, z: String, q: Float) : Base(x,y,z,q) {

}
abstract class SubB(x: Int, y: Char, z: String, q: Float) : Base(x,y,z,q) {

}
class SubASubA(x: Int, y: Char, z: String, q: Float) : SubA(x,y,z,q) {

}
class SubASubB(x: Int, y: Char, z: String, q: Float) : SubA(x,y,z,q) {

}
abstract class SubBSubA(x: Int, y: Char, z: String, q: Float) : SubB(x,y,z,q) {

}
abstract class SubBSubB(x: Int, y: Char, z: String, q: Float) : SubB(x,y,z,q) {

}
like this is so terrible
and it is not even half of my real use case hierarchy
it makes me not want to use this language for this project
r
Inheritance is normally a bad idea… use composition with delegation and the problem may well go away.
r
inheritance is the most natural way to represent the thing I am building
it is a language lexer
there are hierarchies of types of expressions
operators > binary > infix > plus > minus > unary > increment > negation >
the character '+' is an infix operator, which means it is a binary operator, which means it is an operator.
that is obviously inheritance
and if I have a
token
object who is a
BinaryOperator
token, I should be able to say
if token is Operator
, and have that return true, as BinaryOperators are a subclass of Operators. Composition does not feature this
is/instanceof
operation upwards like inheritance does.
a
What about separating the concerns? On one hand, you want to have a hierarchy of operators. A sealed interface is a good fit for that, especially because in the future an operator might fit under two categories.
Copy code
sealed interface Operator {
  sealed interface Binary : Operator
  sealed interface Unary : Operator

  data object Plus : Binary
  data object Minus : Binary
  data object Increment : Unary
  data object Negation : Unary
}
And then use a separate class to hold the data that the operators will use
Copy code
class OperationData(
  val x: Int,
  val y: Char,
  private val z: String,
  private val q: Float,
)
1
then it's up to you whether to use a Operator extension function to handle the OperationData
Copy code
fun Operator.execute(data: OperationData): OperationResult {
  return when (this) {
    Operator.Minus     -> TODO()
    Operator.Plus      -> TODO()
    Operator.Increment -> TODO()
    Operator.Negation  -> TODO()
  }
}
Or add a function to Operator, to be implemented by the subtypes
Copy code
sealed interface Operator { 
  fun execute(data: OperationData): OperationResult
}
r
In this approach, how would I add a
match(string) Boolean
method to each concreete operator?
a
Well, the straightforward answer would be the same as the execute(data) function above: either an extension function, or a function in Operator. But reading between the lines, I guess you're trying to parse some string into a representation? In which case, I would probably again separate the concerns (parsing a string & computing a value). Parsing a string into code can be tricky, so I'd use a library like Parsus, which has a demo which I guess is similar to what you're doing. But if you forced me to write a custom implementation (please don't!), I'd follow a guide like https://craftinginterpreters.com/contents.html