Understanding Lambda/Closure, Part 4 - Learning from Scala



In the previous "Understanding Lambda/Closure" series, we use JavaScript and Python to demonstrate what lambda/closure is and how to use them. That's a good start to learn about lambda/closure, because they are dynamically-typed languages. You don't have to care about types in these languages. When getting into statically-typed languages, we know that type information is necessary for compilers to check a large class of type errors at compilation time. This is a good thing, because you can catch any error early and reduce the cost of errors. When the cleanness comes into the question, however, statically-typed languages are always criticized for their lengthy type declarations.

Let's see how to define a function in Scala.

def max(m: Int, n: Int): Int = if(m > n) m else n

Scala is a statically-typed language, so we have to declare what type a parameter is. It seems the type declaration doesn't cause a big problem in this example. Well, let's see how to write an anonymous function and assign to a variable.

val max: (Int, Int) => Int = (m: Int, n: Int) => if(m > n) m else n

Oh...what a big stuff! You have to say that the type of max is (Int, Int) => Int - a specific function type says that a function accepts two Int arguments and returns a Int value. When defining an anonymous function, you also have to declare parameter types, such as (m: Int, n: Int) => if(m > n) m else n. If you want to define a parameter which accepts a callback function, you have to declare its function type, too. For example:

def bubbleSort(arr: Array[Int], order: (Int, Int) => Boolean): Unit {
    ...
    val o: Boolean = order(a, b)
    ...
}

The order parameter accepts a function which accepts two Int parameters and returns a Boolean value. The following code shows how to call the bubbleSort function.

val arr: Array[Int] = Array(2, 5, 1, 7, 8)
bubbleSort(arr, (a: Int, b: Int) => a > b)

If the lengthy syntax is definitely necessary in Scala, do you want to use lambda/closure? Fortunately, Scala compiler is clever enough to do type inference. It can infer the type information from the context of the source code, so you don't always have to provide the type information when declaring a variable or writing an anonymous function. For example, the above code may be rewritten as follows:

val arr = Array(2, 5, 1, 7, 8)
bubbleSort(arr, (a, b) => a > b)

Scala compiler infers the arr type is Array[Int] from Array(2, 5, 1, 7, 8), so we don't have to declare it again. Also, Scala complier infers both parameters of the anonymous function are Int from arr, so we only have to provide the parameter names and the function body. In Scala, actually, you can even use shorter code to call the function. For example:

val arr = Array(2, 5, 1, 7, 8)
bubbleSort(arr, (_: Int) > (_: Int))

Or, even shortest code:

val arr = Array(2, 5, 1, 7, 8)
bubbleSort(arr, _ > _)

I don't want to explain how Scala plays these magic. If you're interested, take a look at Scala Goosip. The point here is that, the abilities of type inference are important for statically-typed languages. It can provide short and clean syntax while type information is necessary, such as declaring a variable or writing an anonymous function. Lambda/closure is an expressive tool. Without type inference, the lengthy syntax will keeps developers out of using lambda/closure.

The next article will talk about lambda/closure in Java, but we'll look its old proposal first. This will help us understanding how lambda/closure evolved into the current state in JDK8.