Understanding a parameter that is initialized as an extension function

后端 未结 2 2026
爱一瞬间的悲伤
爱一瞬间的悲伤 2021-02-10 11:03

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

What does the .() mean?. I believe the dot r

2条回答
  •  滥情空心
    2021-02-10 11:39

    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.

提交回复
热议问题