Beginner's question: What is the value of "def?"

I’m still learning here. In my second month, I’m wondering why I should use methods. I’m going through the process of eliminating them and putting my non-i/o functions into vals.

The vals end up being typed: I=>R that’s a bit clumsy. Otherwise, things are much easier. Step two is to eliminate “{}” as much as possible.

In the past week, I’ve reduced the size of my class files in half. I only had four weeks of code, so it isn’t a big thing. But I’ve found it a useful way of reducing the problem to simplicity. If a method doesn’t fit well into a val, it needs to be rewritten. Methods do allow overloading. That makes some things easier. I’m split on if that is a good thing or not. I’m currently reserving methods for things that are largely about state.

Is my approach a bad thing? I don’t have a good principled rule for using functions vs. methods. That in itself is a good sign I don’t understand the issue well enough. Any thoughts?

It is better to use methods. There are a number of reasons:

  1. they are more powerful
    E.g. you cannot express polymorphic and dependently typed functions with function literal syntax
// polymorphic
def listHead[A](list: List[A]): A = list.head

Will be fixed in Scala 3

// dependently typed
trait Decoder[Input] {
  type Output
  def decode(input: Input): Output
def decode[A](input: A)(implicit decoder: Decoder[A]): decoder.Output = decoder.decode(input)

Fixed in Scala 3

  1. Defs are available on class level whereas vals are available on class instance level. Vals are initialized in order and if you use uninitialized val there will be NullPointerException:
class Test1 {
  val a = plusOne(1)
  val plusOne: Int => Int = i => i + 1
class Test2 {
  val a = plusOne(1)
  def plusOne(i: Int) = i + 1

Test1 will fail while Test2 will not.

Use vals for functions when you operate with functions as values: when you pass them around, transform and return as a result.

Functions are instances of classes anyway and invoking a function always lead to invoking its apply method. Invoking apply method usually can be shortened by omitting .apply. Look at this:

val function = (x: Int) => x + 1
// expressions below are equivalent

So in fact by using a function instead of a method you’re going through one additional layer of indirection (which often costs CPU cycles). Also keep in mind that functions take space.

class A(param: String) {
  def method1(params) = <something>
  def method2(params) = <something>
  def methodN(params) = <something>

class B(param: String) {
  val function1 = (params) => <something>
  val function2 = (params) => <something>
  val functionN = (params) => <something>

Instances of class B will be much heavier, because each val adds to the instance size, whereas defs don’t. Closures are especially heavy as the non-global state must be separately captured in each function object that needs it.

Another very important thing is interoperability with existing classes, especially Java ones. You can’t implement a def with parameters as a val. What val and var do is only memoization of value, that’s the only difference from def (when talking about class members). So for example you can’t do:

abstract class A {
  def compute(params): ReturnType

class B extends A {
  val compute = (params) => <body>

What you can do is instead:

abstract class A {
  def compute: ParamsTypes => ReturnType

class B extends A {
  val compute = (params) => <body>

But having an abstract function is something rare.

Thank you all.

It does make it a pain to pass functions around, but I’ll have to use the eta expansion until 3.