DEV Community

Daniel Toni
Daniel Toni

Posted on

What Is a Function in Scala

One of the most important concept in Scala and Functional Programming, is that functions are first-class values. This means functions are treated as any other data type in the language: they can be assigned to variables, passed as arguments to other functions, and returned from functions.

Treating functions as first-class values enables powerful code design capabilities, such as:

  1. Higher-Order Functions
  2. Function Composition
  3. Closures and Currying

The way Scala achieves this feature is by treating functions as objects, instances of a class, just like String, Int or any other type.

A function is an object with one method.

First, let's look at the long way of building a function value. We can define a generic trait called MyFunction that takes a parameter of type A and returns a value of type B. This trait will expose an apply method:

trait MyFunction[A, B] {
  def apply(arg: A): B
}
Enter fullscreen mode Exit fullscreen mode

We can then create a variable and instantiate an anonymous class using this trait:

val doubler = new MyFunction[Int, Int] {
  override def apply(arg: Int): Int = arg * 2
}

val randomNumber: Int = 14

doubler.apply(randomNumber) // 28
Enter fullscreen mode Exit fullscreen mode

Notice how doubler is an object with an apply method. The Scala compiler provides syntactic sugar that allows us to omit the explicit .apply invocation, resulting in doubler(randomNumber). This matches the exact syntax we use to call methods, making every function value look and feel callable.

Fortunately, we don't need to define these traits ourselves. The Scala standard library provides built-in traits named FunctionX, such as Function1[A, B] and Function2[A, B, C]. The number in the trait name represents how many parameters the function accepts. Thus, our custom MyFunction[A, B] is functionally equivalent to Function1[A, B] (though the standard library traits bundle additional useful helper methods).

To illustrate this with multiple parameters, here is how we create a function that adds two integers:

val adder = new Function2[Int, Int, Int] {
  override def apply(a: Int, b: Int): Int = a + b
}

val anotherNumber: Int = 12

adder(randomNumber, anotherNumber) // 26
Enter fullscreen mode Exit fullscreen mode

This demonstrates that every function value under the hood is an instance of a built-in FunctionX trait.

The => syntactic sugar for function types

Writing out FunctionX can become verbose. To make code more concise, the Scala compiler provides syntactic sugar using the => operator to define function types:

Int => Int                 // sugar for Function1[Int, Int]
(Int, Int) => Int          // sugar for Function2[Int, Int, Int]
Enter fullscreen mode Exit fullscreen mode

Using this notation, our adder definition becomes:

val adder = new ((Int, Int) => Int) {
  override def apply(a: Int, b: Int): Int = a + b
}
Enter fullscreen mode Exit fullscreen mode

Defining function values with function literals

While the anonymous class syntax works, Scala offers an even cleaner way to produce the exact same object: function literals (also known as lambda expressions).

val doubler = (arg: Int) => arg * 2

val adder = (a: Int, b: Int) => a + b
Enter fullscreen mode Exit fullscreen mode

Though the syntax is completely different, the end result is identical. The compiler abstracts away the boilerplate, instantiating the appropriate FunctionX trait under the hood and overriding the apply method with your function's body. We write the logic, and the compiler writes the new ... { override def apply... } boilerplate for us.

Functions as first-class values

Because functions are objects, we can effortlessly pass them as arguments to other functions:

List(1, 2, 3).map(doubler)

or

List(1, 2, 3).map(n => n * 2)
Enter fullscreen mode Exit fullscreen mode

def vs val

It is important not to mix up two distinct concepts: val functions and def methods. While we often call both "functions", they behave differently under the hood:

A val holds data, an instance of a FunctionX object.

A def defines a method. A method belongs to a class or object, does not hold data on its own, and is not an instance of FunctionX.

When you pass a method to a place that expects a function value, the compiler automatically converts that method into a function object. This mechanism is called Eta-expansion. (I will discuss in a future post).

Summary

A function in scala is an object, an instance of a FunctionX trait whose job is its apply method. => is sugar for the type, f(x) is sugar for f.aaply(x), and a function literal is a concise shorthand the compiler translates into a full function object at runtime

Top comments (0)