Emulating a `super` of values

#1

Currently we cannot refer to a super of values. I propose that the compiler will emulate this by replacing a value with an inlined definition and a private value.
For example, we can replace:

class Foo {
  val f = 0
}

class Bar extends Foo {
  override val f = super.f + 1 //error
}

with this:

class Foo {
  private val _f = 0
  @inline def f = _f
}

class Bar extends Foo {
  private val _f = super.f + 1
  @inline override def f = _f
}
#2

That doesnt appear to work with separate compilation. It may only be workable if the parent is sealed and yhe child in the same file.

#3

To be more specific, I am assuming the inner val is private and thus doesnt have an accessor by default and is hidden from children, and the proposed one is added for children after it is needed.

Vals are private for good reason, exposing them to child classes should be explict.

#4

Is it also true for lazy vals?

#5

Why do you want to override a val?

I’ve been following the rule that if it may be overridden, it should be a def.

#6

I want it evaluated once

#7

Lazy val always has an accessor.

I may be mixing up private and private[this] a bit in my previous reply.
For non-lazy val, I have seen some discussion about the presence or absence of accessors when private of late: Change `private` to mean `private[this]`?

A truly private val doesn’t need a byte code level accessor in most cases (more complex cases, like nested classes may need accessors or bridges).

At the bytecode level, accessors can have the same name as the class field – the uniform access principle is at the Scala language level, not the JVM level, where fields and methods have distinct namespaces. So in some sense, your proposal already exists when there are accessors (e.g. public val) but super is still not allowed.

Accessor or not, I think accessing a super val when defining a child can be made to work if its protected or public or (maybe) in a sealed hierarchy.
I suspect this would make initialization issues more common if used heavily though – even though subclasses are initialized after their parents, there are many ways to get in trouble, and vals on traits would be problematic.

For your exmple specifically, if you “override” a val, you are really creating a new val and overriding the def accessor to redirect from the parents val to the childs while simultaneously providing the initial value. The actual val from the parent still exists in the class – at the bytecode level children can only append fields to a class, they can’t override them.

#8

override a def with a lazy val.

If the parent needs to be evaluated once too, you may need a trait with the def on it that is overriden.

#9

How about, instead of overriding the val, override a def that initializes it?

class Foo {

** lazy val f: Int = initF**

** protected def initF: Int = 0**

}

class Bar extends Foo {

** override protected def initF: Int = super.initF + 1**

}

#10

Overriding a protected def is what I do when I need this pattern.

#11

One limitation currently is you cannot override a lazy val with another lazy val.

I can’t see any reason why this limitation has to be in place. In fact, Mill explicitly does some macro magic to turn its def foo = T{ ... } tasks into lazy val semantics, and it works great!

#12

That’s fine, if you assume there is only a single access to super.initF. I would rather this limitation didn’t exist in the compiler if it’s possible to do so.