This is the part 8 of series Java to Kotlin - Learning Simplified. I hope you are learning something new from every part.
If you have not read part 7, please do here.
Index of this Part: Lambda Expression & Scoped Functions
Lambda Expression π€― π₯
The simplest definition of lambda expression is βA function with no nameβ. In java if a function is accepting an interface with 1 method, we can pass a lambda instead of an anonymous class. It is even shorter to write than anonymous class. In java, a function can not specify it needs a lambda. Letβs see snippet of code.
interface Logger{
public void log();
}
// assume a function
public void callLogger(Logger log){}
callLogger(new Logger(){}) // explicit anonymous object
callLogger(()->{}) // no arg lambda
π‘ Kotlin changed the syntax little bit. In Kotlin, a function can expect lambda with following syntax:
// Kotlin, doSomething is expecting a function which takes no input and returns Unit, block is just the placeholder
fun doSomething(block:()->Unit) {
block() // the function is invoked
}
Now we can call the function doSomething
:
fun anotherFun(){} // accepts no arg and returns Unit
fun performAction(){
doSomething(::anotherFun) β
another fun is a matching function
doSomething({ β
passing a no-arg lamda
})
doSomething{ β
if lambda is last arguement, we can skip the parenthesis
}
}
In reality, Kotlin is also doing similar magic as Java does. To satisfy the needs, Kotlin standard package has a lot of interfaces, check kotlin.jvm.functions
and the lambda is converted to instance of one of the Function#
interface.
// Kotlin: Calling the function with explicit anonymous object
doSomething(object : Function0<Unit>{
override fun invoke() {
}
}
The above code is same as
// Kotlin
doSomething {
}
// the lambda is anonymous instance of Function0
I urge you to check package kotlin.jvm.functions
Understanding concept of calling lambda and the syntax is must for the next section
Scoped Functions π€― π€― π₯
let, run, with, apply, and also
, Refer Original Documentation Here
Understanding lambda expression and syntax is a must for this section. As we saw in the last section, in Kotlin a function can declare that it is expecting a lambda and the syntax to call is funtion name followed by {}
where {}
is the lambda (in reality, an anonymous instace of some interface , check Function#
as suggested above)
The scoped functions are really nice utilities Kotlin offers. There are many blogs to explain them, here we will try to explain the inner working of the functions. Letβs start.
// Kotlin let function (simplified syntax)
fun <T, R> T.let(block: (T) -> R): R {
return block(this)
}
π Here let function is defined as an extension function
(check the syntax T.
) to any class. It means any object can call the let with .
as member.
π The function is accepting a function which accepts one argument and returns another type. Check Function
interface.
π The function accepts the caller as argument.
π In block(this) this
is the caller being passed
π The default name of the argument passed in lambda is it
if there is only one argument
The use of let
// Kotlin
val name = "Gaurav"
name.let {
it.length
}
fun safeCall(var name:String?) { // nullable
name?.let {
it.length // it is safe to call it inside
}
}
Letβs understand what this lamda is doing.
π We used name
which is String to call let, Let is expecting a lambda which accepts the same argument which is caller. In our case name
π The default name of the argument passed to lambda is it
if there is only one argument expected
We can change the name of the argument passed in the Lambda
val name = "Gaurav"
name.let { abc ->
abc.length
}
π Let is expecting a lambda, in our case a String input and R
output
π Kotlin is smart to deduce the type R
with the last statement of the lambda, in our case Int
So R
becomes Int
π The last statement is the return type
// Now the return type is String
name.let { abc ->
abc.length
"Khanna"
}
Another similar scoped function is also
// Kotlin also function (simplified syntax)
fun <T, R> T.also(block: (T) -> R): R {
block(this)
return this
}
The only difference is it returns this
which means the caller object itself and not the lambda result
Before we proceed further and see other scoped functions, I want to introduce a special syntax with Lambda
// Kotlin
fun<T> lambdaSample(block: T.()->Unit){
}
π€― π₯ In the above snippet, the function is expecting a lambda inside the type T
. It means it can not invoke block()
without instance of type T
. As this lambda is supposed to be member of the T
you can refer the calling instance as this
inside the lambda. You can think Kotlin has added an extension function of signature lambda expects in the Type T
if it helps.
fun <T> lambdaWithCaller(obj: T, block: T.() -> Unit) {
obj.block()
}
// call sample:
val name = "Gaurav"
Singleton.lambdaWithCaller<String>(name) {
val lengthOfString = length β
implicit call to member
val lengthOfString1 = this.length β
explicit call to member
}
Note: in the snippet above we called length
inside lambda because length is member of the String class
run
scoped function
// Kotlin
fun <T, R> T.run(block: T.() -> R): R {
return block()
}
The run
function is defined as extension function to any class (T.
) and it accepts the lambda under member of the caller T.()
. It calls the block and returns the lambda result. You can use the caller as this
inside lambda.
// Kotlin
fun <R> run(block: () -> R): R {
return block()
}
This is another variant of the run function. It does not need a caller, all it needs is a lambda as argument and it returns the almbda result
apply
scoped function
// Kotlin
fun <T> T.apply(block: T.() -> Unit): T {
block()
return this
}
The apply
function is also defined as extensiion function T.
and it accepts the lambda under member of the caller T.()
. It calls the block but does not return the result.It returns the caller itself.
also
scoped function
// Kotlin
fun <T> T.also(block: (T) -> Unit): T {
block(this)
return this
}
The also
function is defined as an extension function T.
and it accepts a normal one arg lambda. It calls the lambda and passes the caller as argument. Thatβs why the argument caller can be accessed as it
inside the lambda. also
returns the caller itself.
with
scoped function
// Kotlin
fun <T, R> with(receiver: T, block: T.() -> R): R {
return receiver.block()
}
with
function is not extenstion function, so it does not need any reference caller. Any object call call it as static. It accepts the lambda under member of the caller T.()
. It calls the lambda and returns the result. The receiver object passed is avaialable as this
inside the lambda.
The above explanation is to tell you what is happening under the hood. I recommend reading the official documentation and more blogs on this topic. Note: there is a table Function selection in the end of official docs, it is very useful.
Summary
This part might seem difficult to grasp at first sight. We covered lambda expressions
, and scoped functions
. This is very important topic in Kotlin. Kindly read again if you have doubts.