On Using `def` vs `val` To Define Abstract Members in Scala Traits

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:

Why you shouldn’t use `val` to define fields in traits

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 a def, but a def cannot override a val

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, a val, a lazy val, or an object. So it’s the most abstract form of defining a member. Since traits are usually abstract interfaces, saying you want a val 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