That's related to the Liskov Substitution Principle: you can't assign weaker access privileges in subclass (even for Java). Making var
a def
makes the setter def x_= (y: T ): Unit
private (as @Staix said). So in case when Seadan Car
has formal type Car
- it should not be accessed, but compiler can't find such cases in general (only formal types are known in compile-time), so such behavior is disabled like any weaker privilege:
val car2 = new SeadanCar
car.age = 422 //compiler error, can't mutate "def age"
val car: Car = new SeadanCar
car.age = 42 //now you had mutated immutable
The point of Substitutability Principle is behavior should not be changed after casting to the supertype.
On the other hand, scala could override only getter part of variable as @Régis Jean-Gilles said, but this is not so obvious solution, because intuitively user expects a var
becoming immutable after override def
. And it's actually violating Uniform Access Principle as you have to see your var
as two services (reader and writer) instead of one it really is, while UAP-compatibilty requires the opposite: both reader and writer should be represented by one uniform notation.
P.S. Actually your question points to scala's UAP-compatibility incompleteness. As i said, overriding only var
's reader and leaving writer as is will be inconsistent with UAP - it's blocking ability to override var
itself (so you can't override var in both ways: computational and storage-like), even regardless that you already can't override it due to LSP. But current scala's solution is also problematic. You may find that you can:
trait Car { def age: Int = 7; def age_=(a: Int) = {}}
class SeadanCar extends Car { override def age: Int = 5}
But can't
// just repeating your example
trait Car { var age: Int = 7 }
class SeadanCar extends Car { override def age: Int = 5}
So scala's inheritance seems not to be compatible with UAP. IMHO, the big problem is that reader and var itself have identical names - so you can't distinguish them (when defining, not accessing). I'd solve it with something like:
trait Car { def age_: Int = 7; def age_=(a: Int) = {}}
class SeadanCarReadOnly extends Car { override def age: Int = 5} //can't compile as reader is closed
class SeadanCarVar extends Car { override var age: Int = 5}
class SeadanCarReadOnly extends Car { override def age_: Int = 5}
trait Car2 { var age = 100500 }
class SeadanCarReadOnly extends Car2 { override def age_: Int = 5}
Note, that overriding age_
in my proposed example should lead to:
scalaxx> (new SeadanCarReadOnly).age //call age_ here
resxx: Int = 5
scalaxx> (new SeadanCarReadOnly).age_
resxx: Int = 5
Not like:
trait Car2 { @BeanProperty var age = 100500 }
class SeadanCarReadOnly extends Car2 { override def getAge: Int = 5}
//which leads to inconsistency:
scala> (new SedanCar()).age
res6: Int = 30
scala> (new SedanCar()).getAge
res7: Int = 54
Of cource, such approach should disable overriding var age
and def age_; def age_=
simultaneously:
trait Car2 { var age = 100500 }
class SeadanCarReadOnly extends Car2 {
override var age = 17;
override def age_: Int = 5 //should be compile error here
}
but this is hard to quickly implement it in Scala language due to backward compatibility
P.S./2 Just to mention, regarding mutability/immutabilty part of the question, you definetely can't do this (due to LSP):
trait Car { var age: Int = 32 } //or without initial value
class SedanCar extends Car { override val age = 42 }
And, due to LSP + UAP, shouldn't be able to do this:
trait Car { def age: Int = 7; def age_=(a: Int) = {}}
class SedanCar extends Car { override val age = 42 }
regardless the fact that you can :)