Intermediate: Lambda expressions with receiver
In this chapter, you'll learn how to use receivers with another type of function, lambda expressions, and how they can help you create a domain-specific language.
Lambda expressions with receiver
In the beginner tour, you learned how to use lambda expressions. Lambda expressions can also have a receiver. In this case, lambda expressions can access any member functions or properties of the receiver without having to explicitly specify the receiver each time. Without these additional references, your code is easier to read and maintain.
The syntax for a lambda expression with receiver is different when you define the function type. First, write the receiver that you want to extend. Next, put a .
and then complete the rest of your function type definition. For example:
This function type has:
MutableList<Int>
as the receiver.No function parameters within the parentheses
()
.No return value:
Unit
.
Consider this example that draws shapes on a canvas:
In this example:
The
Canvas
class has two functions that simulate drawing a circle or a square.The
render()
function takes ablock
parameter and returns an instance of theCanvas
class.The
block
parameter is a lambda expression with receiver, where theCanvas
class is the receiver.The
render()
function creates an instance of theCanvas
class and calls theblock()
lambda expression on thecanvas
instance, using it as the receiver.The
main()
function calls therender()
function with a lambda expression, which is passed to theblock
parameter.Inside the lambda passed to the
render()
function, the program calls thedrawCircle()
anddrawSquare()
functions on an instance of theCanvas
class.Because the
drawCircle()
anddrawSquare()
functions are called in the lambda expression with receiver, they can be called directly as if they are inside theCanvas
class.
Lambda expressions with receiver are helpful when you want to create a domain-specific language (DSL). Since you have access to the receiver's member functions and properties without explicitly referencing the receiver, your code becomes leaner.
To demonstrate this, consider an example that configures items in a menu. Let's begin with a MenuItem
class and a Menu
class that contains a function to add items to the menu called item()
, as well as a list of all menu items items
:
Let's use a lambda expression with receiver passed as a function parameter (init
) to the menu()
function that builds a menu as a starting point:
Now you can use the DSL to configure a menu and create a printMenu()
function to print the menu structure to the console:
As you can see, using a lambda expression with receiver greatly simplifies the code needed to create your menu. Lambda expressions are not only useful for setup and creation but also for configuration. They are commonly used in building DSLs for APIs, UI frameworks, and configuration builders to produce streamlined code, allowing you to focus more easily on the underlying code structure and logic.
Kotlin's ecosystem has many examples of this design pattern, such as in the buildList()
and buildString()
functions from the standard library.
Practice
Exercise 1
You have a fetchData()
function that accepts a lambda expression with receiver. Update the lambda expression to use the append()
function so that the output of your code is: Data received - Processed
.
Exercise 2
You have a Button
class and ButtonEvent
and Position
data classes. Write some code that triggers the onEvent()
member function of the Button
class to trigger a double-click event. Your code should print "Double click!"
.
Exercise 3
Write a function that creates a copy of a list of integers where every element is incremented by 1. Use the provided function skeleton that extends List<Int>
with an incremented
function.