Chapter 10: Higher-order functions:Lambdas as parameters and return values
Introduces higher-order functions as a way to use lambdas in your own abstractions, beyond collection and standard-library functions.
After this chapter you will be able to
- Higher-order function basics. Explain that a higher-order function is a function that takes a lambda as an argument or returns one, so lambda-based abstractions are not limited to the standard library.
- Higher-order function definition. Explain that a higher-order function takes a function as an argument, returns a function, or both, and identify why filter is one.
- Function type declaration syntax. Describe how a function type specifies parameter types and a return type, and explain how an explicit variable type lets Kotlin infer the lambda’s parameter types.
- Nullable function type placement. Distinguish a function type with a nullable return type from a nullable variable that holds a function.
- Calling a function argument. Explain how a higher-order function invokes a function passed to it, using the operation on 2 and 3 as an example.
- Predicate-based string filtering. Trace how the simple String filter uses a passed predicate to decide which characters appear in its result.
- Java and Kotlin lambda interoperability. Explain how Kotlin lambdas can be passed to Java methods expecting functional interfaces and how Java lambdas can be passed where Kotlin expects function types.
- Function types as generated interfaces. Describe how Kotlin function types are represented by FunctionN interfaces and what happens when a function value is called.
- Default function-type parameters. Explain why joinToString gives its transform parameter a default lambda and describe how callers can use or replace that default.
- Nullable function-type parameters. Describe why a nullable function-type parameter cannot be called directly and how to call it only when present; distinguish a nullable function from its non-nullable return value.
- Function-valued return types. Describe how to declare and return a function from another function, using the shipping-cost example.
- State-dependent predicate selection. Explain how the contact-list example returns a predicate based on UI filter options and passes it to filter.
- Reusable site visit analysis. Explain why parameterizing a site-visit calculation by OS removes some duplication but cannot express conditions such as multiple mobile OSs or iOS visits to the signup page, and how a lambda parameter addresses that limit.
- Inline function substitution. Describe what the compiler does when a function is marked inline and why that can preserve reusable library logic without a function call.
- Function body substitution. Explain that an inline function’s body is copied into each call site rather than invoked normally, so separate calls with different lambdas are inlined independently.
- Lambda inlining conditions. Distinguish passing a lambda expression directly to an inline function from passing a function-type value held in a variable: the first can be inlined with the function body, while the second is called as usual.
- Limits on inlining lambda parameters. Explain why an inline function can inline a lambda it calls directly or passes to another inline function, but not one it stores for later use. Describe why Sequence.map’s transform lambda needs a non-inline representation.
- Selective inlining with noinline. Describe how noinline lets an inline function exclude particular lambda parameters from inlining, and when that is useful.
- Inline collection operation performance. Explain why using the standard library’s filter function can generate roughly the same bytecode as manually filtering a list.
- Chained operations and sequences. Explain why chaining inline filter and map can still create an intermediate collection, and when a sequence may be preferable despite its own overhead.
- Choosing functions to inline. Explain why functions that take lambdas are stronger candidates for inline than ordinary functions. Describe why other uses need measurement rather than a blanket rule.
- Lambda-based resource management. Explain how a function can take the operation on a resource as a lambda while handling acquisition and release around it, and describe withLock as the Lock extension for this pattern.
- Closable resource cleanup with use. Describe how use runs a lambda on a closable resource and ensures the resource is closed whether the lambda finishes normally or throws, without requiring Java-style try-with-resources syntax.
- Non-local returns from lambdas. Explain what an unqualified return does in a lambda passed to forEach, and compare it with returning from a regular loop.
- Inlining requirement for outer returns. Explain why returning from the enclosing function is possible for a lambda passed to an inline function but not for one passed to a non-inline function.
- Local returns with labels. Explain that a labeled return stops the lambda, not its enclosing function, and describe how to write an explicit label or use the function name as a label.
- Local returns from anonymous functions. Explain where an unlabeled return inside an anonymous function goes, how that differs from a lambda, and why anonymous functions can simplify multiple early returns.
What it builds on
- Introduction to lambdas: Blocks of code as values (chapter 5) · Introduces lambdas as blocks of code used as values.
- Removing and transforming elements: filter and map (chapter 6) · filter is a collection operation the reader has encountered.
- Syntax for lambda expressions (chapter 5) · Introduces lambda expression syntax.
- The Unit type: Kotlin’s void (chapter 8) · Explains Unit as the type for a function with no meaningful return value.
- Making possibly null variables explicit with nullable types (chapter 7) · Introduces nullable types.
- Initializing and configuring objects: The apply function (chapter 5) · The use of apply introduces lambdas that operate with a receiver.
- Passing a lambda as a parameter to a Java method (chapter 5) · Passing Kotlin lambdas to Java methods through SAM conversion
- Default parameter values (chapter 3) · Default parameter values
Section by section
Introduction
Introduces higher-order functions as a way to use lambdas in your own abstractions, beyond collection and standard-library functions.
- Higher-order function basics: Explain that a higher-order function is a function that takes a lambda as an argument or returns one, so lambda-based abstractions are not limited to the standard library.
Why it matters: It connects the lambdas you’ve already used to functions you can define yourself.
Key terms: lambdas, higher-order functions
10.1 Declaring functions that return or receive other functions: Higher-order functions
Defines higher-order functions in Kotlin and identifies familiar standard-library examples.
- Higher-order function definition: Explain that a higher-order function takes a function as an argument, returns a function, or both, and identify why filter is one.
Why it matters: This definition helps you recognize when a Kotlin function works with other functions as values.
Key terms: higher-order function, lambda, function reference, predicate function, filter
10.1.1 Function types specify the parameter types and return values of a lambda
This section explains how to write function types for lambdas and how parentheses distinguish a nullable return value from a nullable function variable.
- Function type declaration syntax: Describe how a function type specifies parameter types and a return type, and explain how an explicit variable type lets Kotlin infer the lambda’s parameter types.
Why it matters: This notation lets you declare variables and parameters that hold lambdas. - Explicit Unit return type: Explain why Unit must appear in a function type for a function that returns no meaningful value.
Why it matters: It prevents an easy mistake when writing a type for a lambda used only for its action. - Nullable function type placement: Distinguish a function type with a nullable return type from a nullable variable that holds a function.
Why it matters: The position of the question mark changes what may be null.
Key terms: function type, parameter types, return type, type inference, Unit, nullable return type, nullable variable
10.1.2 Calling functions passed as arguments
This section shows how a higher-order function calls a function argument, using string filtering to illustrate the call and receiver access.
- Calling a function argument: Explain how a higher-order function invokes a function passed to it, using the operation on 2 and 3 as an example.
Why it matters: This is the basic step that lets a higher-order function use behavior supplied by its caller. - Predicate-based string filtering: Trace how the simple String filter uses a passed predicate to decide which characters appear in its result.
Why it matters: The filter example shows how calling a function argument controls a concrete result. - Outer receiver access: Explain why the filter implementation uses a labeled this expression inside buildString.
Why it matters: It clarifies which object the filtering code iterates over while it builds the result. - Function type parameter names: Distinguish the readability benefit of named function-type parameters from their role in type matching.
Why it matters: It prevents descriptive parameter names from being mistaken for type requirements.
Key terms: higher-order function, function passed as an argument, filter, predicate, buildString, StringBuilder, labeled this expression, outer receiver, parameter names, function type
10.1.3 Java lambdas are automatically converted to Kotlin function types
Explains how Java lambdas work with Kotlin function types, the Unit-return requirement at that boundary, and how function types are represented by generated interfaces.
- Java and Kotlin lambda interoperability: Explain how Kotlin lambdas can be passed to Java methods expecting functional interfaces and how Java lambdas can be passed where Kotlin expects function types.
Why it matters: These conversions let Java and Kotlin code call each other’s lambda-taking functions. - Unit returns from Java lambdas: Explain why a Java lambda supplied to a Kotlin function type returning Unit must explicitly return Unit rather than void.
Why it matters: The return type matters even when a Java lambda is otherwise compatible with a Kotlin function type. - Function types as generated interfaces: Describe how Kotlin function types are represented by FunctionN interfaces and what happens when a function value is called.
Why it matters: This representation explains why function values can be called and used wherever an interface can be used.
Key terms: SAM conversion, functional interface, function type, receiver object, Unit, void, FunctionN, Function0<R>, Function1<P1, R>, invoke
10.1.4 Parameters with function types can provide defaults or be nullable
This section shows how function-type parameters can provide a default lambda or accept null, using string conversion in joinToString as the example.
- Default function-type parameters: Explain why joinToString gives its transform parameter a default lambda and describe how callers can use or replace that default.
Why it matters: A default lambda makes customization available without complicating the common call. - Nullable function-type parameters: Describe why a nullable function-type parameter cannot be called directly and how to call it only when present; distinguish a nullable function from its non-nullable return value.
Why it matters: The null check prevents an absent callback from being invoked while preserving what its return type guarantees when it is present.
Key terms: function type, default value, transform lambda, joinToString, nullable function type, invoke, safe-call syntax, non-nullable return type
10.1.5 Returning functions from functions
This section shows how to return a function value and use it to select behavior based on program or UI state.
- Function-valued return types: Describe how to declare and return a function from another function, using the shipping-cost example.
Why it matters: Returning a function lets code choose a calculation now and use it later. - State-dependent predicate selection: Explain how the contact-list example returns a predicate based on UI filter options and passes it to filter.
Why it matters: A returned predicate keeps the choice of filtering rules separate from applying them to the contacts.
Key terms: function type, getShippingCostCalculator, lambda, member reference, getPredicate, predicate, filter, prefix, ContactListFilters
10.1.6 Making code more reusable by reducing duplication with lambdas
The section shows how passing a lambda as a condition makes site-visit analysis reusable when a simple data parameter is not flexible enough.
- Reusable site visit analysis: Explain why parameterizing a site-visit calculation by OS removes some duplication but cannot express conditions such as multiple mobile OSs or iOS visits to the signup page, and how a lambda parameter addresses that limit.
Why it matters: It helps you recognize when a lambda can replace copied versions of the same calculation. - Lambda strategies instead of classes: Describe how function types and lambdas can simplify the strategy pattern compared with declaring an interface and separate implementations.
Why it matters: It connects behavior passed as a lambda to a familiar way of varying an algorithm.
Key terms: function types, lambda expressions, higher-order functions, strategy pattern
10.2 Removing the overhead of lambdas with inline functions
This section explains why lambdas can add runtime overhead and how the inline modifier avoids a function call by substituting a function’s code at each call site.
- Lambda runtime overhead: Explain why using a lambda can be less efficient than executing the same code directly, including the effect of captured variables.
Why it matters: This overhead explains the motivation for inline functions. - Inline function substitution: Describe what the compiler does when a function is marked inline and why that can preserve reusable library logic without a function call.
Why it matters: Inlining offers a way to extract repeated logic into a function while avoiding the call described here.
Key terms: lambda expression, anonymous classes, captures, inline modifier, function call
10.2.1 Inlining means substituting a function body to each call site
Explains how inline functions substitute their bodies at call sites and when a lambda argument can be inlined with them.
- Function body substitution: Explain that an inline function’s body is copied into each call site rather than invoked normally, so separate calls with different lambdas are inlined independently.
Why it matters: This explains what declaring a function inline changes in the resulting code. - Lambda inlining conditions: Distinguish passing a lambda expression directly to an inline function from passing a function-type value held in a variable: the first can be inlined with the function body, while the second is called as usual.
Why it matters: It prevents you from assuming that every function-type argument to an inline function is itself inlined.
Key terms: inline function, call site, lambda expression, function type
10.2.2 Restrictions on inline functions
This section explains when lambda parameters can be inlined, how to exclude individual parameters from inlining, and where inline functions can be called.
- Limits on inlining lambda parameters: Explain why an inline function can inline a lambda it calls directly or passes to another inline function, but not one it stores for later use. Describe why Sequence.map’s transform lambda needs a non-inline representation.
Why it matters: It explains both the compiler’s restriction and why a sequence operation may retain a lambda instead of inlining it. - Selective inlining with noinline: Describe how noinline lets an inline function exclude particular lambda parameters from inlining, and when that is useful.
Why it matters: It lets you keep a function inline without requiring every lambda parameter to be inlined. - Cross-module and Java calls: State how inline functions work across Kotlin modules and what happens when Java calls one.
Why it matters: An inline function remains usable across module boundaries and, in most cases, from Java, though Java calls do not receive inlining.
Key terms: inline parameter, Illegal usage of inline-parameter, Sequence.map, transform, noinline modifier, lambda parameters, inline functions, modules, third-party libraries, Java
10.2.3 Inlining collection operations
This section explains why inline collection operations can be efficient, while chained operations may still create intermediate collections that make sequences worthwhile for large inputs.
- Inline collection operation performance: Explain why using the standard library’s filter function can generate roughly the same bytecode as manually filtering a list.
Why it matters: You can use idiomatic collection operations without assuming a manual loop will be faster. - Chained operations and sequences: Explain why chaining inline filter and map can still create an intermediate collection, and when a sequence may be preferable despite its own overhead.
Why it matters: Inlining does not eliminate every allocation, so choosing a sequence depends on the size of the collection.
Key terms: inline, filter, lambda, map, intermediate collection, asSequence, sequence
10.2.4 Deciding when to declare functions as inline
This section explains when declaring a function inline is likely to help and why bytecode size matters to that decision.
- Choosing functions to inline: Explain why functions that take lambdas are stronger candidates for inline than ordinary functions. Describe why other uses need measurement rather than a blanket rule.
Why it matters: It helps you avoid adding inline everywhere in the hope of making code faster. - Bytecode size trade-off: Explain the code-size cost of inlining a large function and describe the suggested way to keep the inline portion small.
Why it matters: An inline function’s performance benefit must be weighed against the code it duplicates.
Key terms: inline keyword, lambda arguments, JVM, bytecode size, call site, non-inline function
10.2.5 Using inlined lambdas for resource management with withLock, use, and useLines
This section shows how inline higher-order functions manage locks and files while keeping resource cleanup out of the operation’s code.
- Lambda-based resource management: Explain how a function can take the operation on a resource as a lambda while handling acquisition and release around it, and describe withLock as the Lock extension for this pattern.
Why it matters: It explains why the locking and file examples can keep resource-handling code separate from the operation itself. - Closable resource cleanup with use: Describe how use runs a lambda on a closable resource and ensures the resource is closed whether the lambda finishes normally or throws, without requiring Java-style try-with-resources syntax.
Why it matters: It lets you describe why file-handling code remains safe even if the operation fails. - Specialized file line access: Distinguish useLines from use by describing the resources it applies to and what its lambda receives.
Why it matters: It shows when a more specific standard-library function makes file-reading code more concise.
Key terms: resource management, try/finally, withLock, Lock, use, Closeable, inline, try-with-resources, useLines, File
10.3 Returning from lambdas: Control flow in higher-order functions
Introduces the question of how return expressions behave when lambdas replace loops, without yet explaining the answer.
10.3.1 Return statements in lambdas: returning from an enclosing function
This section explains why an unqualified return in a lambda passed to an inline function can exit the enclosing function, and why that return is not allowed for a lambda passed to a non-inline function.
- Non-local returns from lambdas: Explain what an unqualified return does in a lambda passed to forEach, and compare it with returning from a regular loop.
Why it matters: It lets you tell whether replacing a loop with forEach changes where a return takes the program. - Inlining requirement for outer returns: Explain why returning from the enclosing function is possible for a lambda passed to an inline function but not for one passed to a non-inline function.
Why it matters: The inline requirement explains when a non-local return is permitted rather than leaving it as an arbitrary rule.
Key terms: return, lambda, forEach, non-local return, inline, non-inline function
10.3.2 Returning from lambdas: Return with a label
This subsection explains how labels make returns local to a lambda and how the same labeling rules apply to a lambda receiver’s this expression.
- Local returns with labels: Explain that a labeled return stops the lambda, not its enclosing function, and describe how to write an explicit label or use the function name as a label.
Why it matters: Labels let you skip work inside a lambda without returning from the surrounding function. - Labeled receiver references: Describe how a label can identify the implicit receiver of a lambda with a receiver in a this expression.
Why it matters: The same label idea helps identify which receiver a this expression refers to.
Key terms: local return, label, return@label, forEach, lambdas with receivers, implicit receiver, labeled this expression
10.3.3 Anonymous functions: Local returns by default
This section presents anonymous functions as another way to pass code to functions, emphasizing their syntax and local return behavior.
- Anonymous function syntax and types: Describe how an anonymous function is written and how its return type is specified for block and expression bodies.
Why it matters: This helps you recognize anonymous functions as passable blocks of code and read their type declarations. - Local returns from anonymous functions: Explain where an unlabeled return inside an anonymous function goes, how that differs from a lambda, and why anonymous functions can simplify multiple early returns.
Why it matters: The return target determines whether only the passed block ends or the enclosing function ends.
Key terms: anonymous function, block body, expression body, return type, return expression, fun keyword, early return statements, labeled
10.4 Summary
Where this is used later
- Reified type parameters come with restrictions (chapter 11) · Restrictions on inline functions and the noinline modifier.
- Functions with reified type parameters can refer to actual type arguments at run time (chapter 11) · Inlining can provide performance benefits when lambda arguments are inlined.
- Keep cancellation in mind when acquiring resources (chapter 15) · The use function for resource management.
Concepts explained in this chapter
- inline functionAn inline function is a Kotlin function whose body can be substituted at its call sites by the compiler and which can declare reified type parameters.
- function typeA function type describes the parameter types and return type of callable values.
- higher-order functionA higher-order function is a function that accepts functions as arguments, returns functions, or both.
- non-local returnA non-local return exits the enclosing named function from within an invoked lambda rather than merely returning from the lambda.
- lambdaA function literal is source syntax that directly expresses a function value, including its parameters and body.
- predicateA predicate function is a function that evaluates an input and returns a Boolean indicating whether it satisfies a condition.
- method invocationThe invoke method is a Kotlin function named invoke that enables instances of its containing type to be called with function-call syntax.
- this expressionA labeled this expression is a Kotlin expression that refers to the receiver associated with a specified label.
- FunctionN interfaceA FunctionN interface is a compiler-generated Kotlin interface representing a function value with a specific number of parameters and an invoke operation.
- finallyA finally block is an exception-handling block that executes after a protected block regardless of whether an exception was thrown.
- filteringFiltering is the operation of selecting or excluding items from data or a collection according to specified criteria.
- default valueA default parameter is a function parameter with a value automatically supplied when the caller omits that argument.
- safe-call operatorThe safe-call operator `?.` conditionally invokes a method or accesses a property only when its receiver is non-null, otherwise producing null.
- first-class objectA first-class function is a function that can be stored in variables, passed as an argument, or returned from another function.
- sequenceA sequence is an ordered computation that produces elements, potentially lazily and without requiring all elements to be stored or computed immediately.
- nullable typeA nullable type is a type whose values may include null and whose unsafe operations are restricted by the type system.
- genericsA generic function is a function declared with one or more type parameters so it can operate on values of different types.
- extensionAn extension function is a Kotlin function defined outside a class that can be called with member-like syntax on values of an existing receiver type.
- terminal operationA terminal operation is a sequence operation that produces a final result and triggers evaluation of the preceding transformations.
- unit typeA unit type is a type with exactly one value, commonly written (), that represents the absence of meaningful information or a meaningful result.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
