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.

πŸ”₯ Hope you learned something, This part was 🀯Kotlin🀯. Here is part 9

Subscribe to newsletter and keep learning good stuff

Email

πŸ‘‰ If you like article, click on β™₯️ recommend and share on social πŸ‘₯ media.
πŸ‘‰ Feel free to comment πŸ’¬
πŸ‘‰ Follow me πŸ‘€ for the more interesting articles. Twitter Medium
πŸ‘‰ Subscribe to newsletter and keep learning