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
Canvasclass has two functions that simulate drawing a circle or a square.The
render()function takes ablockparameter and returns an instance of theCanvasclass.The
blockparameter is a lambda expression with receiver, where theCanvasclass is the receiver.The
render()function creates an instance of theCanvasclass and calls theblock()lambda expression on thecanvasinstance, using it as the receiver.The
main()function calls therender()function with a lambda expression, which is passed to theblockparameter.Inside the lambda passed to the
render()function, the program calls thedrawCircle()anddrawSquare()functions on an instance of theCanvasclass.Because the
drawCircle()anddrawSquare()functions are called in the lambda expression with receiver, they can be called directly as if they are inside theCanvasclass.
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.