Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding a parameter that is initialized as an extension function

Tags:

kotlin

In the following code the parameter type for modelInitializer is CalendarMonthTitleModelBuilder.()

What does the .() mean?. I believe the dot refers to an extension. And when you add () after it, I think it means to create an instance of this type. Does this mean some anonymous extension is being created here and initialized?

inline fun EpoxyController.calendarMonthTitle(modelInitializer: CalendarMonthTitleModelBuilder.() ->
        Unit) {
    CalendarMonthTitleModel_().apply  {
        modelInitializer()
    }
    .addTo(this)
}

What happens if you leave out the dot before ()?

like image 796
Johann Avatar asked Sep 08 '19 06:09

Johann


1 Answers

The correct name for this is called lambda with receiver

You've started in the right direction. So an easy way to think of this is by starting in extension functions:

fun CalendarMonthTitleModelBuilder.foo() = //...

The function foo is an extension function on the type CalendarMonthTitleModelBuilder.

Let's approach it from another angle. Let's talk about higher-order functions, a.k.a. functions that take other functions as parameters:

fun higherOrder(func: () -> Unit) = //...

This function receives a lambda that receives no parameters and returns Unit. What could one do if we wanted to use a CalendarMonthTitleModelBuilder inside the lambda? An easy way is to pass it in:

fun higherOrder(func: (CalendarMonthTitleModelBuilder) -> Unit) = //...

Calling this function, would be something like this:

higherOrder {
   it.someMethod()
}

(here someMethod is part of CalendarMonthTitleModelBuilder)

However, we can somehow make this lambda an extension to CalendarMonthTitleModelBuilder by using a similar sytax to the extension functions:

fun higherOrder(func: CalendarMonthTitleModelBuilder.() -> Unit) = //...

The difference now, is that we've created a lambda with receiver, meaning instead of using the implicit parameter it, we can use this, or better yet, omit it:

higherOrder {
   someMethod()
}

inside the lambda, this is an instance of CalendarMonthTitleModelBuilder, so you can simply call someMethod.

These constructs are often used in DSL's and you see them a lot in examples like yours - with the builder pattern.


Here's a very simple example. Let's assume you have UserBuilder class that builds users and you want to create a small DSL for this (this is an exaggeration of the pattern, but suits to help out I think):

data class User(
    val email: String,
    val password: String)

class UserBuilder {
    var email: String = ""
    var password: String = ""

    fun build() = User(email, password)
}

One can begin by writing a higher-order function like so:

fun user(func: UserBuilder.() -> Unit) =
    UserBuilder().apply(func)

Inside the method, we create an instance of the builder and apply to it the lambda. This is a simple trick so we can keep on chaining the methods and at the end call build. For example:

user {
    email = "[email protected]"
    password = "123456"
}.build()

It's possible to go even further using extension functions:

fun UserBuilder.withEmail(emailBuilder: () -> String) {
  email = emailBuilder()
}

Which let's you do:

user {
    withEmail {
        "[email protected]"
    }
}.build()

we can call withEmail inside user because withEmail is an extension function on UserBuilder and inside user this is of type UserBuilder due to the lambda with receiver.

You can do something similar to the password.

like image 63
Fred Avatar answered Nov 01 '22 16:11

Fred