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
https://dotty.epfl.ch/docs/reference/new-types/dependent-function-types.html

  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
function.apply(5)
function(5)

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.