Scala Methods vs Functions
Today let’s explore what the differences are between Functions and methods within Scala. Specifically we’ll be exploring:
- What are the fundamental differences?
- Using Methods as Function parameters
- What is the Scala compiler doing under the hood?
The differences:
Well… a method is a method which is part of a class. It is built into the language.
A Function on the other hand is an object which has an apply method. Therefore you can pass a Function around because it is treated just like any other Object in Scala.
Let’s explore the syntax used to define them:
Method:
- Start with def keyword
- allows default values
- very easy to read and figure out what the inputs are / what it evaluates to
e.g.
def add(p1: Int, p2: Int) : Int = {
p1 + p2
}
Function:
val addFunc = (p1: Int, p2: Int) => p1 + p2
Using some syntactic sugar we can come up with the above which you could argue is just as readable as the method example, however, I already find this a little more difficult to read and this is only a simple example.
For instance let’s look at a function that takes another function as an argument:
val hof = (p1: Int, p2: Int, transform: (Int, Int) => Int) => {
transform(p1, p2)
}
as we can see there are just an extra few symbols ‘()’ and ‘=>’ but this is enough to make my brain lose track momentarily while evaluating the above code.
Let’s see the above example as a method:
def hof(p1: Int, p2: Int, transform: (Int, Int) => Int) => {
transform(p1, p2)
}
much better.
Using methods as Functions
As we saw in the previous examples, methods are great for readability but you can only pass Functions around, not methods.
In this previous code example, transform is a parameter of type Function2 ( 2 inputs, 1 output ) — I could have specified this like so:
def hof(p1: Int, p2: Int, transform: Function2[Int, Int, Int]) = transform(valOne, valTwo)
we could call this with the previous add function created:
val addFunc = (valOne: Int, valTwo: Int) => valOne + valTwo
val sum = hof(1, 2, addFunc)
However we could also call hof with a method to use as the transform argument:
def add (valOne: Int, valTwo: Int) = valOne + valTwo
val sum = hof(1, 2, add)
look at that, it just works
…
how?
In the above example, the compiled code will first create a Function2 object which matches the same parameters and return type of the method Add. The function implementation ( apply method ) will then be used to call the original method.
See bonus round at end for viewing bytecode decompiled showing the above.
Converting methods to Scala
Scala has a few different mechanisms which allow you to convert methods to Functions, we just explored one use case in which the Scala compiler does this implicitly, let’s explore a few more within REPL:
- Assign it to a variable with a matching Function type
def add (valOne: Int, valTwo: Int) = valOne + valTwo
val myFunc: Function2[Int, Int, Int] = add
If you provide a wrong type, you will get an error message, e.g.
scala> val myFunc: (Int, Int, Int) => Int = add
^
error: missing argument list for method add
Unapplied methods are only converted to functions when a
function type is expected.
You can make this conversion explicit by writing `add _` or
`add(_,_)` instead of `add`.
2. Use an underscore after the method name
scala> val myFun = add _
val myFun: (Int, Int) => Int = $Lambda$1086/0x00000008010c9028@51aaa9d4
Bonus Round
Let’s look at Scala code compiled and then decompiled back to Java:
class Conversion {
def hof(f: () => Unit):Unit = {}
def myFunc() : Unit = { }
def convert(): Unit = { hof(myFunc) }
}
In this example we’re interested in the convert method:
- It calls hof with the method myFunc
We can compile this with the scalac command
scalac Conversion.scala
and then decompile the .class file produced back to Java using IntelliJ.
This will produce the following Java code for the convert method:
public void convert() {
this.hof(() -> {
this.myFunc();
});
}
🎉 we can now see what we discussed earlier, a Function ( In this case a Lambda ) is passed to hof and within it’s implementation it calls the original method myFunc!
One final point:
- Methods work as normal with Generics
- Annonymous functions do not
E.g.
def test[A, B] = (p1: A, f: (A) => B): B = {
f(p1)
}