When I update the Scala Cookbook, I need to update Recipe 8.2, “How to use abstract and concrete fields in Scala traits.” That recipe is written particularly with an OOP developer in mind, and I didn’t write about handling the same situation from an FP perspective.
Short story: Use `def` to define fields in your traits
The short story is that if you want to declare an abstract member in a trait, and that member may later be fulfilled by a def
or val
field in an implementing class, the most flexible approach is to define the member as a def
field. The primary benefit of this approach is depicted in the following image from an “Effective Scala” talk given by Mirco Dotta:
I’ll expand on this point in this lesson.
“A `val` can override a `def`, but a `def` cannot override a `val`”
A good way to think about this situation is with a quote from this Stack Overflow page:
“A
val
can override adef
, but adef
cannot override aval
”
val
can override a def
The “val
can override a def
” part of that statement is demonstrated with this code:
// val can override def
trait SentientBeing {
def id: Int
}
class Person extends SentientBeing {
val id = 2
}
That code compiles and works as expected.
def
cannot override a val
The “def
cannot override a val
” statement can be shown by attempting to write code like this:
trait SentientBeing {
val id: Int
}
class Person extends SentientBeing {
// won't compile.
// error: “method `id` needs to be a stable,
// immutable value”
def id = 2
}
As the comments show, attempting to create code like this results in a compiler error. As the error message implies, using val
implies a guarantee that the variable is stable and immutable, and a def
does not imply that same guarantee. If you make id
a var
field in SentientBeing
, that error message will go away. (But Scala/FP developers don’t use var
fields.)
More motivation to use `def` in traits
A comment on this Stack Overflow page makes a few more good points about why you should use a def
field in a trait:
“A def can be implemented by either of a
def
, aval
, alazy val
, or anobject
. So it’s the most abstract form of defining a member. Since traits are usually abstract interfaces, saying you want aval
is saying how the implementation should do.”
From the standpoint of writing really meaningful code, the last two sentences in that paragraph are good. They get to the essence of creating a field like this in a trait:
- What is my intent?
- What am I really trying to express about the
id
field in the trait?
I used to program very casually in Java — such as not worrying about marking fields private
or final
, etc. — but as I get older (and hopefully wiser), I’ve learned that with Scala you can be very clear about what you’re trying to communicate to yourself and other developers by taking a few moments to think about issues like this. You should be able to say to other developers, “I did that intentionally, and here’s why ...”
More information
If you want to dig into this topic more, I encourage you to read the links below, and also use the usual tools to examine how the compiler translates your Scala code into bytecode:
- Use
scalac -Xprint:all
when compiling your code - Disassemble your bytecode with
javap -c
- Use JAD or a similar tool to decompile your class files to see how the code is implemented in Java
As just a hint of what you’ll see, when you compile the SentientBeing
class when it declares id
as a def
field, you’ll see this output:
abstract trait SentientBeing extends Object {
def id(): Int
}
And when you declare it as a val
field, you’ll see this output:
abstract trait SentientBeing extends Object {
<stable> <accessor> def id(): Int
}
See also
- Mirco Dotta’s Effective Scala slides
- A Stack Overflow Q&A on When to use a val or def in traits
- Choices with def and val in Scala
- Functional Programming, Simplified