Lambda Expressions
A few years ago a conference organizer asked if I’d like to talk about Lambada expressions. Dancing is a form of expression, but I’m far from being qualified to talk about it—I chose lambda expressions instead. Lambdas are also a form of expression—they’re concise, expressive, and elegant.
Lambdas are short functions that are used as arguments to higher-order functions. Rather than passing data to functions, we can use lambdas to pass a piece of executable code to functions. Instead of using data to make decisions or perform calculations, the higher-order functions can rely on the lambdas to make decisions or perform calculations. In essence, it’s like instead of giving someone a fish, you’re giving them a fishing lesson. Let’s dive into lambdas.
Structure of lambdas
A lambda expression is a function with no name whose return type is inferred. Generally a function has four parts: name, return type, parameters list, and body. Lambdas carry over only the most essential parts of a function—the parameters list and body. Here’s the syntax of lambdas in Kotlin:
{ parameter list -> body }
A lambda is wrapped within {}
. The body is separated from the parameter list using a hyphenated arrow (->
), and the body is generally a single statement or expression, but it may also be multiline.
When passing a lambda to a function as argument, avoid the urge to create multiline lambdas unless it’s the last argument. Having multiple lines of code in the middle of the argument list makes the code very hard to read, defeating the benefits of fluency we aim to get from lambdas. In such cases, instead of multiline lambdas, use function references, which we’ll see in Using Function References.
Passing lambdas
Thinking functionally and using lambdas takes some effort and time to get comfortable with. Given a problem, we have to think in terms of transformational steps and look for functions that can help us with intermediate steps.
For example, let’s implement a function, in the functional style, to tell if a given number is a prime number or not. Let’s first formulate the problem in words and then translate that into code. Think of the simplest way to find if a number is a prime number—no fancy optimization for efficiency needed here. A number is a prime number if it is greater than 1 and is not divisible by any number in the range between 2 and the number. Let’s reword that: a number is a prime number if greater than one and none (nothing) in the range 2 until n
divides the number. The functional-style code becomes visible with one more coat of polish over those words:
fun isPrime(n: Int) = n > 1 && (2 until n).none({ i: Int -> n % i == 0 })
Let’s discuss how the lambda is passed as a parameter in this code and how, in this example, the functional style is as efficient as an imperative implementation would be. The code snippet 2 until n
returns an instance of the IntRange
class. This class has two overloaded versions of none()
, where one of them is a higher-order function that takes a lambda as a parameter. If the lambda returns true
for any of the values in the range, then none()
returns false
...