Scala: Should I use a method or a function?

Posted on June 1, 2016

This question is often raised on forums, and there is a great deal of confusion in the context of the functional programming what’s the difference is and what should be used where. Scala has two function-like entities:

Both of them can be used in a functional context:

Similar, isn’t it? Let’s look closer.

m here is a method, the very same class method we are familiar with in Java. It’s not an object, can’t be passed anywhere and has no value. In fact it belongs to the class Test and only makes sense when calling it as testInstance.m(...) . And indeed, decompiled bytecode shows it as public int m(int); in class Test .

But we pass it to map , don’t we? First, let’s comment out def y and look at class files:

Test$$anonfun$1.class Test.classs

Nothing too interesting: our Test class and anonymous function f in Test$$anonfun$1 . Now let’s return def y back:

Test$$anonfun$1.class Test$$anonfun$y$1.class Test.class

Wait, what is Test$$anonfun$y$1 ? When Scala sees a method in a functional context it defines anonymous function class (a class derived from Function1 in this particular example), which has apply method, and that apply method simply calls testInstance.m(...) ( testInstance is passed as an argument to anonymous function constructor so it can be used later). Almost the same thing happens with f , an anonimous function class is defined with the corresponding apply method. There is one big difference though if we look at bytecode for x and y :

Every time I call y a new instance of class Test$$anonfun$y$1 is created and initialized! While in x nothing like this happens as anonymous function was instantiated when I declared f , so the code simply calls this.f to retrieve pre-initialized value.

Given this my suggestion would be to use explicit functions whenever higher-order functions/methods are involved and not rely on implicit conversions, and use methods in all other cases.

Does it “no longer matter” with introduction of Scala 2.12 which compiles to Java 8 lambdas? Let’s look at Java 8 bytecode. The good part is that Scala 2.12 doesn’t create class definitions for anonymous functions, instead it’s using factories to create dynamic classes on the fly. At least JAR becomes smaller and loading potentially faster. Instead Scala creates methods in class Test which are called from a Function1 object:

Compiler is smart enough to optimize out the instance of Test in the first case (i.e. the method is static ) which saves it from passing an object reference in this particular case. Unfortunately in the second case this is required since we are going to call m . Let’s look how these functions are called:

vs

First of all, what is this invokedynamic call? JVM is using a factory to create a lightweight object with apply method. Factory arguments look like this (for f ):

Basically it tells JVM how to initialize an object. But invokedymanic is not just initializing the object, it actually creates a code snippet based on the factory data and replaces invokedynamic with it so it doesn’t need to touch the factory again. Well, it’s still not an object but inline code, so every type invokedynamic is called it will initialize a new lightweigth object with apply method. So the mechanism is the same as in 2.11, only lambdas are much lighter and don’t require full classes.

What about those cycles? The difference is still there. x is simply using f initialized when constructing Test object. y is initializing lightweight anonymous function every time y is called, and also wrapping a call to m in another method, namely Test$$$anonfun$2(int) .

Does it all really matter? Well, I wouldn’t be eager to replace methods used in higher-order method/functions in existing code (unless it’s in performance-crytical code like Spark, but even then benchmarks should probably be done first). But whenever I have a choice between two options and there is no other reason except for historical I would use functions.

“Entities must not be multiplied beyond necessity” (c) Occam

Let’s do some simple benchmarking (2.12.0-M4):

Minimum will probably take into account warm-up and occasional GC. Let’s even run it 10 times and take minimum again. Here’s the result:

x: 6669954 y: 6962329 (+4.4%)

Not much and it’s a special case, but it’s noticeable.