Understanding a parameter that is initialized as a

2020-06-28 10:38发布

问题:

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 ()?

回答1:

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 = "foo@bar.com"
    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 {
        "foo@bar.com"
    }
}.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.



回答2:

First of all ()->Unit is a lambda. And this lambda has a this, and it's type is the CalendarMonthTitleModelBuilder. It's called lambda with a receiver.

So inside modelInitializer you can access to CalendarMonthTitleModelBuilder as you are inside this class: using this.

For example, I assume that CalendarMonthTitleModelBuilder has a someMethod:

val epoxyController: EpoxyController = EpoxyController()

epoxyController.calendarMonthTitle {
    // inside these curlies `this: CalendarMonthTitleModelBuilder = CalendarMonthTitleModel_()`
    this.someMethod() //you can use 'this' inside this lambda
    someMethod() // or as usual just skip writing 'this', just access `CalendarMonthTitleModelBuilder` methods as you inside it's class

}

This feature is essential in building DSLs.

Here is a link to documentation.



标签: kotlin