First-Class Functions
A Java lambda expression is written with the arrow operator ->, with a dummy parameter on the left and the computing expression on the right.
Why Function Names Are Optional
Most of the functions you have written so far were given a name the moment they were defined. That habit feels natural, but it hides a question: does a function have to have a name to be useful? This article starts from code that does not need one.
Many learners meet lambda expressions in Java and quickly conclude that Java now treats functions the way languages such as Lisp or JavaScript do: as ordinary values that can be stored, handed around, and invoked by name. The conclusion feels natural. A lambda expression is a compact way to write down a function, and it can be assigned to a variable. The rest of this article examines why that conclusion is not quite right, and what Java actually does instead.
What do you think happens?
A lambda expression for squaring is assigned to a variable named sqr. Can you then write sqr(42) to call it, the same way you would call a named function?
Reveal answer
No, Java does not allow sqr(42). The call is not permitted.
Assigning a lambda to a variable does not make Java treat that variable as a callable function. The reason is strong typing: a variable must be declared with a type, and a plain assignment leaves open what type fits a function. Java's answer to that question is the functional interface, which the next part of the course covers.
Tracing the sqr Variable
Follow the situation the source describes, step by step. First, a squaring expression is written as a lambda and assigned to a variable named sqr. At this point sqr holds a lambda expression. Second, you try to use sqr the way you would use a function, by writing a call with an argument. Java rejects that call.
The rejected call is exactly what a learner would expect from a first-class function, which is why the situation is confusing. A call by name looks like it should work, because the variable clearly refers to something that computes a square. The missing piece is not the lambda itself. The missing piece is the type of the variable.
Java is a strongly typed language. To have a variable named sqr, you must declare that variable and give it a type. So the question becomes: what type fits a value that is a function? The source answers with a construct called a functional interface. The arrow in the lambda tells you how the function is written. The type tells Java what kind of value the name refers to. A plain assignment leaves that second part unsettled, and that is why the call fails.
Reading the Arrow Notation
A Java lambda expression is built around a single operator, the arrow written as ->. The operator is what marks the whole construction as a lambda expression. Everything to the left of the arrow is the dummy parameter for the function. Everything to the right of the arrow is the expression that computes the function's value.
The word dummy matters. The parameter on the left is a placeholder: it names the input the function will receive, but it does not hold a real value until the function is used. The right-hand expression describes what to do with that input. A squaring function therefore has one placeholder for its input, and an expression on the right that multiplies that placeholder by itself.
The idea is older than Java. The mathematician Alonzo Church studied computable functions and needed a way to talk about a function without fixing a name for it, and he used the Greek letter lambda as the symbol for this. In mathematical notation, lambda (x). x^2 reads as the function of x given by x^2. The word lambda announces that a function is being defined. The parenthesized name is the parameter, and what follows the period is the expression that gives the result.
lambda (x). x^2Python spells the same idea with the keyword lambda, followed by parameters, a colon, and a single expression. The colon plays the role that the period plays in the mathematical notation, and the Java arrow plays the same role of separating the parameter from the expression.
Lambdas as Function Literals
The source describes lambda notation as a kind of function literal. A literal is notation that stands for a value directly. The number 42 is an integer literal: it represents a value of type int. In the same way, a function literal represents a value of type function. Having function literals is the starting point for thinking of a function as just another kind of data value.
A language has first-class functions when functions can be treated as data values, for example by assigning them to variables, passing them as parameters, returning them, or storing them in arrays.
Naming a function makes the name a permanent part of the program, which is unnecessary for a function used only once. The source makes this point with a squaring example: a definition that names the function square works, but the name then persists for the whole program even when the function is needed only once. A notation that defines the function without naming it avoids that permanent name.
Passing an Unnamed Function Along
Passing a function as a parameter works the same way as passing a number, except that the thing passed is behavior. The receiving subroutine can then call the function it was given. The following Python program passes an unnamed squaring function, written as a lambda, into a function named apply. Read it first and predict the output before looking at the answer.
def apply(f, value):
return f(value)
print(apply(lambda x: x * x, 42))- 1def
apply(f, value)defines a subroutine that expects a function as its first parameter. - 2return f(value) calls whatever function was passed in, using the value that was passed in.
- 3lambda x: x * x creates a function with one dummy parameter x and the expression x * x, without assigning it a name.
- 4
apply(lambda x: x * x, 42)passes the unnamed function and the number 42 into apply. - 5Inside apply, f(42) runs the squaring expression with x set to 42, producing 1764, which apply returns and print displays.
- apply receives a function f and a value
- the lambda is never given a name in this program
1764Trace the flow in the diagram. The main program hands over the behavior and the input together. The apply subroutine does not know in advance what the function does; it simply calls the function it was given. The lambda computes the result, and the value travels back up through apply to the caller.
What First-Class Functions Permit
The source lists the operations that first-class functions allow. A function can be assigned to a variable, passed as a parameter to a subroutine, returned as the value of a subroutine, or placed into an array. Each of these treats the function as ordinary data, in the same way a number or string can be treated.
- Assigning to a variable: the function value is stored under a name.
- Passing as a parameter: the function value is handed to a subroutine, which can call it.
- Returning as a value: a subroutine produces a function value as its result.
- Storing in an array: function values are kept together as elements of an array.
Mistakes Beginners Make With Lambdas
The following mistakes come up often when learners first meet lambda expressions and first-class functions.
Common mistake Assuming a variable holding a lambda can be called with arguments
What it looks likeint result = sqr(42); after sqr = a squaring lambdaWhy it failsAssigning a lambda to a variable does not make Java treat that variable as a callable function. Java is strongly typed, so the variable needs a declared type that fits a function.
Do this insteadTreat the variable as holding a function value whose type is a functional interface, and call the function through the interface's method.
Common mistake Thinking the arrow is part of the computation
Why it failsThe arrow only marks the construction as a lambda. The parameter sits on the left and the computing expression sits on the right.
Do this insteadRead x -> x * x as parameter, arrow, expression, and identify each piece before reading the program.
Common mistake Believing that giving a function a name is always required
Why it failsNaming a function makes the name a permanent part of the program, which is unnecessary for a function used only once.
Do this insteadUse an unnamed lambda when the function is needed in one place, such as an argument to another function.
Common mistake Concluding that Java has full first-class functions because lambdas exist
Practice Predicting an Unnamed Function Call
Predict the output of this Python program before you run it. The function apply receives a function and a value, calls the function on the value, and returns the result. The function passed in adds ten to its input. What does print display? print(apply(lambda x: x + 10, 5))
Hints
- Identify the parameter and the expression in lambda x: x + 10 first.
- Inside apply, f(value) becomes the lambda applied to 5.
- The answer is a single integer.
What do you think happens?
What does print(apply(lambda x: x + 10, 5)) display?
Reveal answer
15
The lambda has the dummy parameter x and the expression x + 10. apply calls it with 5, so f(5) computes 5 + 10, which is 15, and print displays 15.
Key Takeaways
- A Java lambda expression is written with the arrow operator ->, with a dummy parameter on the left and the computing expression on the right.
- Assigning a lambda to a variable does not make the variable callable like a function, because Java is strongly typed and the variable needs a declared type, which is a functional interface.
- A language has first-class functions when functions can be treated as data: assigned to variables, passed as parameters, returned as values, and stored in arrays.
- Lambdas let you pass unnamed behavior into another function, but this alone does not make functions first-class in Java.