1. Overview

In this tutorial, we’ll explain what the apply method is in Scala. We’ll also see how the Scala compiler implicitly adds this method to some objects to make our code simpler and a little more functional.

2. apply Method Overview

Scala is a multi-paradigm programming language designed to integrate features of object-oriented programming and functional programming.

In mathematics and computer science, apply is a function that applies a function to arguments. In Scala, functions, similar to other objects, have a type.

We can define a function that takes one argument:

// explicitly define type
val doubleFunction : Function1[Int,Int] = x => 2 * x 

Now, if we want to call this function, typically, what we’ll do is just call it with an argument:

doubleFunction(2) // returns 4 

Looking at our definition of apply, if Scala truly provides functional programming capabilities, then a function (function object in our case), should itself have a function (method in our case) called apply that takes in the argument to be applied to the function.

Let’s see if that works:

doubleFunction.apply(2) //returns 4

Voila, it works! But how exactly does this work if we did not explicitly define an apply method?

3. apply Method Invocation

We may ask ourselves, what’s the difference between invoking the apply function on a function versus calling the function directly?

The answer lies in how the Scala compiler treats function objects.

Let’s take a look at what the compiler is doing when we call a function directly without using the apply method, taking our simple example from before:

object ApplyFunction extends App {
  val doubleFunction : Function1[Int,Int] = x => 2 * x

  doubleFunction(2)
}

If we run the scalac -Vprint:typer compiler command to print out the compiler’s typer phase, we’ll see somewhere in the output something like:

private[this] val doubleFunction: Int => Int = ((x: Int) => 2.*(x));
<stable> <accessor> def doubleFunction: Int => Int = ApplyFunction.this.doubleFunction;
ApplyFunction.this.doubleFunction.apply(2)

Looking at the last line, we see that even though we didn’t explicitly call the apply method on the function object, the compiler silently and implicitly did that for us.

Scala provides functional programming paradigms and tries to follow the mathematical principle that we described in the introduction by replacing all direct function object invocations with an apply method on the function object instead.

Having to always call apply on a function object would’ve made the language a little tedious and cluttered, which is why the compiler does that job for us.

4. apply() Method on Objects

Any object that has an apply method can be called directly, passing the argument to that object without having to explicitly call the apply method.

Let’s define an object in Scala, give it an apply method, and see if we can just invoke the object directly. First, we’ll start with no apply method to serve as a control experiment.

We’ll define an object Doubler, with a method double that takes an Int:

object Doubler {
  def double(number : Int) : Int = 2 * number
}

Let’s see what happens when we try to call the object directly:

Doubler(2) // compiler error: Doubler.type does not take parameters

The compiler complains that the object Doubler doesn’t take parameters — in other words, it’s not a function.

Now, let’s add an apply function to the object and retry. Our object should now look like this:

object Doubler {
  def double(number : Int) : Int = 2 * number
  def apply(number : Int) : Int = 2 * number
}

If we try to call the object now, it works:

Doubler(2) // returns 4

If we run the scalac -Vprint:typer command, we’ll see this somewhere in the output:

Doubler.apply(2)

When we write Doubler(2), the compiler changes it to Doubler.apply(2), and now that we have the apply method, it compiles successfully.

With this information. we’ll see how case classes use the apply method.

5. apply() Method in Case Classes

When we create a regular class in Scala, we have to use the keyword new to create a new instance of that class. Now, if we define a case class instead, we don’t need the keyword new:

object ApplyFunction extends App {
  val baeldung: Employee = Employee("baeldung")
}
case class Employee(name : String)

Looking at the above example, we can see the similarity with the Doubler object that we created previously. Does this then imply that case classes use the apply function?

The only way we can know this is by attempting to replace the direct call with an apply method and see if that works:

val baeldung: Employee = Employee.apply("baeldung") // compiles

We can see that it compiles and works fine.

We may ask ourselves: We only created a case class — where does the apply method come from? Also, how do we go about instantiating a new class without the use of the keyword new?

Running the compiler command, we see somewhere in the output that an object Employee is created with an apply method taking the parameters needed by the constructor of the Employee class.

Here’s a snippet of the compiler output with unimportant output stripped away:

 case class Employee extends AnyRef with Product with Serializable {
  ...
};
  <synthetic> object Employee extends scala.runtime.AbstractFunction1[String,Employee] with java.io.Serializable {
   ...
    case <synthetic> def apply(name: String): Employee = new Employee(name);
    ...
  }

We immediately see that by simply defining a case class, an object with the name of the case class is also created with an apply method. 

We also notice that the apply method then creates an instance of that class using the new keyword shown above. 

Looking at the compiler output for the instantiation of our class, we see that the compiler uses the apply method to create a new instance of the Employee class:

 private[this] val baeldung: Employee = Employee.apply("baeldung");

Now, what if we want to do this ourselves manually?

What if we don’t want to use a case class but want the ability to create a class without using the new keyword?

We can do so by creating our class and creating an object with the same name as the class (called a companion object) in the same source file, with an apply method that is then used to construct an instance of our class:

object ApplyFunction extends App {
  val blog = Blog("baeldung")
}

class Blog(name : String)
object Blog {
  def apply(name : String) = new Blog(name)
}

This compiles successfully.

6. Conclusion

In this article, we’ve seen what the apply method is in Scala and how the Scala compiler works in our favor to simplify and declutter our code. We’ve seen how case classes, function objects, and regular objects use this method to make our code more functional.

As usual, the source code can be found over on GitHub.


» 下一篇: MUnit 介绍