6

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

Scaramouche
  • 3,188
  • 2
  • 20
  • 46
Johann
  • 27,536
  • 39
  • 165
  • 279
  • 1
    1. the answer is in the title of your question: it's an extension function. 2. Try, and you'll see. – JB Nizet Sep 08 '19 at 06:22
  • 1
    Possible duplicate of [What is a purpose of Lambda's with Receiver?](https://stackoverflow.com/questions/47329716/what-is-a-purpose-of-lambdas-with-receiver) – denvercoder9 Sep 08 '19 at 06:23

2 Answers2

13

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.

Fred
  • 16,367
  • 6
  • 50
  • 65
  • What does DSL stand for? – Johann Sep 08 '19 at 06:46
  • 1
    `domain specific language`, basically you create a specific language to your use case. This is usually manifested in the form of several methods. I've added an example, which hopefully explains it better. – Fred Sep 08 '19 at 06:48
  • 1
    Thanks for the nice explanation. But it really didn't help. I had to take your code samples and actually run them to really understand what was going on and that took me around an hour. Take the case where I remove the dot in user(func: UserBuilder.() -> Unit). It generates an error message of "Top level declaration expected" - That's a totally useless error message. – Johann Sep 08 '19 at 07:57
  • One final note. Personally I find the word "extension" for ```fun user(func: UserBuilder.() -> Unit)``` very misleading as I don't see anything in this that actually extends some existing functionality, whereas ```fun UserBuilder.withEmail``` is clearly extending an existing object. But hey, that's just my opinion. – Johann Sep 08 '19 at 08:18
  • 1
    I understand. By extending I meant that inside the scope of the function, `UserBuilder` has a new anonymous method, meaning the lambda you call is actually on the `UserBuilder` object you've created. Also, since the dot syntax is pretty close to extension functions, I thought it would help. Sorry for the misleading information. – Fred Sep 08 '19 at 13:35
  • is there a equivalent feature for this on java or can someone point me to a reference ? {i am using kotlin based extensions in a java project..} – RAINA Jan 13 '21 at 14:58
0

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.

Max Farsikov
  • 2,451
  • 19
  • 25