This is a normal function
fun uppercaseString(text: String): String {
return text.uppercase()
}
fun main() {
println(uppercaseString(”hello”))
}it also can be written as a lambda expression:
func main() {
val upperCaseString = {text: String -> text.uppercase()}
println(upperCaseString("hello")) // HELLO
}While `text` is the parameter and `String` is a type of the parameter.
it is almost the same as anonymous function in javascript.
const upperCaseString = text => text.toUpperCase();lambda expression can also be passed to another function.
val numbers = listOf(1,-2,3,4,-5,6)
val positives = numbers.filters({n -> n > 0})
val negatives= numbers.filters{n -> n < 0} // remove parenthases allowed
println(positives) // [1,3,4,6]Lambda expression can also be returned from a function. But let understand the function types first. Example from above code
val upperCaseString = {text: String -> text.uppercase()} Kotlin infer the function type of this line, but basically it was something like this
val upperCaseString: (String) -> String = {text -> text.uppercase()}Where `upperCaseString` is the function name, `(String)` is the parameter type and String is the return type which is string.
This upperCaseString function received 1 parameter which is a string, and it return a string.
You must declare parameter and return types either in lambda expression or as a function type.
val upperCaseString = {text: String -> text.uppercase()} // delcared in the lambdaval upperCaseString: (String) -> String = {text -> text.uppercase()} // deleared as a functionthis is not valid and will not work
val upperCaseString = {text -> text.uppercase()} // wont work as none was declared.now let return lambda from a function
fun toSeconds(time: String): (int) -> Int = when(time){
"hour" -> {value -> value * 60 * 60} // this return a lambda
"minute" -> {value -> value * 60} // return a lambda
else -> {value -> value} //return a lambda
fun main() {
val timesInMinutes = listOf(2,10,15,1)
val min2sec = toSeconds("minute")
val totalTimeInSeconds = timesInMinutes.map(min2sec).sum()
println("Total time is $totalTimeInSeconds secs") // Total time is 1680 secs
}Lambda can be invoked separately by adding aprentheses `()` after the curly braces `{}` and including any parameters withing the parenthereses.
println({text: String -> text.uppercase()}("hello")) // HELLOTrailing lambdas
println(listOf(1, 2, 3).fold(0, { x, item -> x + item })) // 6can also be written as
println(listOf(1, 2, 3).fold(0) { x, item -> x + item }) // 6Example:
fun repeatN(n: Int, action: () -> Unit) {
for(i in 1..n){
action()
}
}n → an Int
action → a function
takes no params
returns Unit (like void)
So action is not a value, it is someting that can be called later. Inside `repeatN` we can see action(), kotlin call that function again and again.
how we call this function is
repeatN(5){
println("hello")
} // this is how we do it as trailing lambdabasically kotlin rewrites this mentally as
repeatN(5, {println("hello")})that `{ println(“hello“) }` is a lambda and this lambda matches the type () → Unit
No arguments
returns Unit
Why cannot do this
repeatN(5, println("Hello"))println(“Hello”) runs immediately
prints `Hello` once
returns `Unit`
So basically we do
repeatN(5, Unit)while `repeatN` wants a function. not `Unit`
it must be passed as a function not call it.
{ println("Hello") } A different mental model. Think of it like this
`println(“Hello”) → DO it now
`{println(“Hello“)} → SAVE it and do it later
Java/js equivalent
repeatN(5, () -> System.out.println("Hello")); // java
repeatN(5, () => Console.log("Hello")); // jsif lambda is the only param
fun doSomething(action: () -> Unit) {
action()
}
// can remove parentheses completelt
doSomething{
println("Run!")
}
// instead of this
doSomething({ println("Run!") })with multiple params
fun greet(name: String, action: ()-> Unit) {
println("Hello $name")
action()
}Without trailing lambda
greet("Ashraf", { println("Welcome!") }
// Hello Ashraf
// Welcome!with trailing lambda
greet("Ashraf"){
println("Welcome!")
}Why kotlin do trailing lambda like this? Because lambdas often contain multiple lines:
repeatN(5) {
println("Hello")
println("World")
println("Again")
}compare with this:
repeatN(5, {
println(”Hello”)
println(”World”)
println(”Again”)
})Trailing lamndas remove visual clutter.
Real kotlin examples that we will see everywhere
// repeat
repeat(3) {
println(it)
}
// forEach
list.forEach {
println(it)
}
// run
run {
println("Inside block")
}
// Android / Compose / KMP
Button(onClick = {
println("Clicked")
}) {
Text("Click me")
}
how to declare the function that accept another function:
//not allowed
fun bad(a: () -> Unit, b: Int)
// allowed
fun good(b: Int, a: () -> Unit)mental shortcut, when you see this
someFunction(...) {
// code
}This read as “I am passing a function as the last argument“
Some other usage of lambda in kotlin
fun someFun(n: Int, action: (String) -> Unit) {
action("Hello from kotlin")
}we can call it like this
someFun(3, { s -> println(s) })
// or
someFun(3) { s: String -> println(s) }
// or since the function declaration already have type
someFun(3) { s -> println(s) }
// or since it has only 1 param we can skip the param
someFun(3) { println(it) }common mistake
fun sumFun(n: Int, action: (String, Int) -> Unit) {
for (i in 1..n) {
action()
}
}
action here received no arguments while it expecting 2. a String and an Int
fun sumFun(n: Int, action: (String, Int) -> Unit) {
for (i in 1..n) {
action("Iteration", i)
}
}
sumFun(5) { s, i ->
println("$s $i")
}Multi-parameter example that makes sense
fun repeatWithLabel(n: Int, label: String, action: (String, Int) -> Unit) {
for (i in 1..n) {
action(label, i)
}
}
repeatWithLabel(3, "Step") { label, i ->
println("$label $i")
}
can also use named parameter in function types
fun withUser(action: (name: String, age: Int) -> Unit) {
action("Ashraf", 30)
}
withUser { name, age ->
println("$name is $age")
}
Lambda that return values
fun calculate(a: Int, b: Int, action: (Int, Int) -> Int): Int {
return action(a, b)
}
val sum = calculate(3, 4) { x, y ->
x + y
}
println(sum) // 7
// or single-line
val product = calculate(3, 4) { x, y -> x * y }
We are also allowed to skip or ignore unused parameters using `_`
fun repeatWithLabel(n: Int, action: (String, Int) -> Unit) {
for (i in 1..n) {
action("Step", i)
}
}Ignore the `String`
repeatWithLabel(3) { _, i ->
println(i)
}Ignore the `Int`
repeatWithLabel(3) { label, _ ->
println(label)
}But then I thought if we want to ignore the param, why bother declaring it right? or is there a way to run a function based on the params exist or not. End up we are unable to do it and should not do it. thre are option to design it properly.
Nullable parameter
fun repeatWithLabel(
n: Int,
action: (String?, Int) -> Unit
) {
for (i in 1..n) {
action(null, i)
}
}
// usage
repeatWithLabel(3) { label, i ->
if (label != null) {
println("$label $i")
} else {
println(i)
}
}Overload function which I prefer and very kotlin-idiomatic
fun repeatWithLabel(n: Int, action: (Int) -> Unit) {
for (i in 1..n) {
action(i)
}
}
fun repeatWithLabel(n: Int, action: (String, Int) -> Unit) {
for (i in 1..n) {
action("Step", i)
}
}
// usage
repeatWithLabel(3) {
println(it)
}
repeatWithLabel(3) { label, i ->
println("$label $i")
}
or other option that for complext data (this make my brain spinning), using data class
data class Step(val label: String, val index: Int)
fun repeatStep(n: Int, action: (Step) -> Unit) {
for( i in 1..n ){
action(Step("Step", i))
}
}
// usage
repeatStep(3) {
println(it.index)
}
No posts

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.