Kotlin
2026-08-21 09:00
The previous three units were about declaring and calling.
This one starts the functional side of the language.
The final section — lambdas with receivers —
is the mechanism behind the HTML builder from unit 1.
Read with and apply as the foundation, not the footnote.
with and apply.Storing one is legal and rarely the point. Lambdas are for passing.
Every step mechanical. No magic in between.
In nested lambdas, only the innermost it is accessible.
Where the meaning is not obvious, name the parameter.
The compiler will not stop you. Your reader will.
The last expression is the result.
Same rule as a when branch. Same rule as a function expression body.
Third instance. Kotlin never departs from it.
Kotlin wraps the variable so it can outlive the call.
A lambda that escapes still sees the variable.
Here that is what you want.
Stored in a listener, it keeps the wrapper — and everything it references — alive.
A function value that makes objects. No lambda written at all.
When the lambda would only forward.
When it does anything else, the lambda is clearer —
and contorting it to fit produces something worse.
That reads as a description of the result.
The equivalent loop reads as a procedure, and you have to run it in your head.
Prefer any to a negated all. The double negative is a reading hazard.
Use count, not filter(...).size. filter builds a whole collection to ask its size.
A hand-written loop with a HashMap,
a lookup, a null check, and a list creation on every insertion.
map builds a list. filter builds another.
A hundred elements: irrelevant. A million: two allocations and two traversals.
Intermediate — returns a sequence, does nothing yet.
Terminal — produces a result, forces the chain.
Prints nothing at all.
map(1) filter(1) map(2) filter(4) map(3) filter(9) ...
Not map across everything, then filter across everything.
Order matters. filter before map maps fewer elements.
Eagerly, the total work is identical either way.
Short-circuiting is free. find stops as soon as it has an answer.
Infinite sequences are safe, because nothing runs until asked.
Large collections. Long chains. Chains that short-circuit.
For a list of ten, asSequence() is a pessimisation dressed as an optimisation.
Its lambdas are a compiler feature over functional interfaces.
So a Kotlin lambda must become an instance of one.
Automatic. Which is what makes the Java ecosystem usable without wrappers.
A lambda that captures nothing → one instance, reused.
A capturing lambda → a new object per call.
In a tight loop, that is an allocation per iteration.
The compiler infers the target from a parameter type. Here there is none.
To unregister a listener later,
you need a reference to the exact object you registered.
SAM conversion is for Java interfaces.
Kotlin has proper function types — next unit.
Declaring a single-method interface in Kotlin to accept a lambda is Java in disguise.
An ordinary lambda has parameters.
A lambda with a receiver additionally has a receiver object,
which becomes this inside the body.
Unit 3: a function got a receiver.
Here: a lambda does.
applyreturns the receiver.withreturns the lambda’s result.
Create, configure, use. No intermediate variable, no repeated name.
No compiler support. Nothing special.
They work because a function can declare a parameter of extension function type.
Any function you write can do the same.
Each block is a lambda with a receiver, and the receiver is the enclosing element.
Which is why td is available inside tr and not outside it.
The Kotlin Type System turns to the safety claim.
Nullability, primitives and boxing, Any/Unit/Nothing.
And the read-only versus mutable collection split — which this unit quietly relied on.
Kotlin · Programming with Lambdas