Lambdas and higher-order functions
In Kotlin a function is a value you can store, pass around and hand to something else. This is what those braces you have been giving to filter really are — and it is how every button in Part 3 works.
You have been doing this for two lessons
Look at this again:
prices.filter { it >= 7 }You know what it does. Here is the question we skipped: what, exactly, did you hand to filter?
Not a number. Not a piece of text. You handed over an instruction — a small piece of code that filter then ran, once per item, on your behalf.
That is the idea this lesson is about, and it is one of the big ones. In Kotlin, a function is a value. You can put one in a , pass it as an , and get one back from another function, exactly like a number or a string.
A teacher has two hundred exam papers to mark and an assistant to help.
The teacher does not mark the papers. The teacher hands over the stack and a slip of paper saying how to mark one: give a point for every correct answer, take one off for a blank.
The assistant does all the walking — pick up paper, apply slip, write score, next paper. The slip does all the deciding.
filter is the assistant. { it >= 7 } is the slip. Neither is much use without the other, and the useful part — the thinking — fits on the slip.
A function with no name
A is a function written inline, with no name, between braces:
1val double = { x: Int -> x * 2 }
2println(double(5))That prints 10. Reading it left to right: the inputs, then an arrow, then what to do.
- Everything before the
->is the . - Everything after it is the body.
- The last line of the body is the result. There is no
returnin a lambda.
Once it is in a val, you call it with brackets like any other : double(5).
The type of a function
double has a , just like everything else in Kotlin. Its type is:
val double: (Int) -> Int = { it * 2 }Read (Int) -> Int as "takes an Int, gives back an Int". Once you have written the type on the left, Kotlin knows the parameter is an Int, so you can drop it from the braces — and then becomes available as the automatic name for the single input.
That is the whole rule behind it: one unnamed parameter, and Kotlin calls it it. With two parameters you must name them yourself:
1val add = { a: Int, b: Int -> a + b }
2println(add(2, 3))When a lambda gives nothing back
val shout: (String) -> Unit = { println(it.uppercase()) } is Kotlin's way of writing nothing comes back. println prints and hands back nothing, so this lambda hands back nothing too. You will see () -> Unit constantly in Android: it is the type of "some code to run when something happens".
Higher-order functions
A is one that takes a function as an input. You have used several already — filter, map and sumOf are all higher-order. Now write one:
1fun applyTwice(n: Int, f: (Int) -> Int): Int {
2 return f(f(n))
3}The second is not a number — it is a function. Inside the body, f(n) runs whatever was handed in, and the result goes straight back into f again.
println(applyTwice(3, double))Prints 12: three doubled is six, six doubled is twelve. And because f is just a parameter, the same function does something completely different with a different slip:
println(applyTwice(3, { it * it }))Prints 81: three squared is nine, nine squared is eighty-one.
The trailing lambda rule
That last line has an ugly , { ... }) at the end. Kotlin has a rule to fix it:
If the last argument is a lambda, you may move it outside the round brackets.
println(applyTwice(3) { it * it })Same call, and much easier to read. If the lambda is the only argument, the brackets vanish altogether — which is why you have been writing prices.filter { it >= 7 } all along, with no brackets in sight.
This rule is why so much Kotlin looks like it has custom language keywords in it. It does not. repeat(3) { ... } is an ordinary function call, and so is every Compose layout you will meet in Part 3.
When a lambda does nothing but pass its input straight to an existing function, you can name that function directly with two colons:
names.forEach(::println)
This is a function reference, and it means exactly the same as names.forEach { println(it) }. Use whichever reads better.
Why this matters for real apps
Almost everything interactive in Android is a function you hand over so that somebody else can run it later. That is called a :
1Button(onClick = { score = score + 1 }) {
2 Text("Roll")
3}onClick is a parameter of type () -> Unit. You are not clicking anything now — you are handing Compose a slip of paper that says what to do when someone taps. Compose keeps it and runs it at the right moment.
Once you see that shape, an enormous amount of Android stops looking like magic. It is parameters, all the way down.
- Open Pocket Studio, tap Projects, and open your Kotlin practice project.
- Tap Editor and open
Playground.kt. - Select everything in the file and delete it.
- Type the program from the walkthrough above.
- Tap Run and read the Output panel.
You should see exactly this:
112
281
3tick 1
4tick 2
5tick 3- Now add a line at the end of
main:println(applyTwice(10) { it - 1 }). - Run it. You get
8— ten minus one is nine, nine minus one is eight. You changed whatapplyTwicedoes without touchingapplyTwiceat all. - Try breaking it: change that new lambda to
{ it > 1 }and run. It refuses to compile, because a lambda giving backtrueorfalseis not an(Int) -> Int. Put it back.
val double = { x -> x * 2 }. Standing on its own, nothing tells Kotlin what kind of thing x is.{ x: Int -> x * 2 } — or declare the type on the left: val double: (Int) -> Int = { it * 2 }. Inside a call like filter, this never happens, because the list already says what the items are.it only exists when the lambda has exactly one parameter and you have not named it. With two parameters, or once you write x ->, there is no it.{ a, b -> a + b }.return f instead of return f(n). Kotlin can see you meant to run it.return f(n). (Handing the function itself back is legal too, but then the return type must be (Int) -> Int rather than Int.)applyTwice(3, double) { it * it }.applyTwice(3, double) or applyTwice(3) { it * it }. The trailing lambda is the last argument, not an extra one.- A is a function with no name, written in braces: parameters,
->, body. The last line of the body is the result. - Functions are values. They have like
(Int) -> Int, and can be stored, passed and returned. - is the automatic name when a lambda has exactly one unnamed parameter.
- means nothing is handed back.
() -> Unitis the type of "code to run later". - A takes a function as a parameter —
filterandmapare ones you already use, and now you can write your own. - The rule moves a final lambda outside the brackets, which is why so much Kotlin looks like braces.
- Next: extension functions, which let you add a function to a type you did not write, and the five scope functions you will see in every Android codebase.