ceiling cat
ceiling cat

Reputation: 564

Which kotlin language feature is this

I'm learning about the kotlin DSL, specifically with Teamcity and I see an initialization pattern I don't really understand yet

Kotlin playgound link

Here is the code

package org.arhan.kotlin

fun main() {
    val project = project {
        configuration {
            step {
                name = "step 1"
                command = "hi"
            }
            
            customstep {
                name = "flang"
                anotherCommand = "derp"
                command = "1111"
            }
        }
    }
    println(project.configurations[0].steps[1].command)
}

fun project(block: Project.() -> Unit): Project {
    return Project().apply(block)
}

fun Project.configuration(block: Configuration.() -> Unit): Configuration {
    val configuration = Configuration().apply(block)
    configurations.add(configuration)
    return configuration
}

fun Configuration.step(block: Step.() -> Unit): Step {
    val step = Step().apply(block)
    steps.add(step)
    return step
}

class Project {
    var configurations = mutableListOf<Configuration>()

    fun build(block: Configuration.() -> Unit) = Configuration().apply(block)
}

class Configuration {
    var steps = mutableListOf<Step>()
}
 
open class Step {
     final lateinit var name: String 
     var command: String = ""
}

open class CustomStep(): Step(){
    var anotherCommand: String = ""
    constructor(init: CustomStep.() -> Unit): this(){
        // what does this do?
        init()
    }
}

fun Configuration.customstep(block: CustomStep.() -> Unit): Step {
    // how is this constructor initialized
    val step = CustomStep(block)
    steps.add(step)
    return step
}

Specifically the question is about how the CustomStep class is initialized. It take's in a lambda with CustomStep as the reciever (is this the correct terminology?).

And then I call init() in the constructor, which initializes the newly created CustomStep based on the block that was passed in.

I'm not sure how that initialization works. Or rather, which specific Kotlin language feature is being used here.

And how is this different if instead I wrote it the following way?

open class CustomStep(): Step(){
    var anotherCommand: String = ""
    // no constructor
}

fun Configuration.customstep(block: CustomStep.() -> Unit): Step {
    // use apply vs. passing in the block
    val step = CustomStep().apply(block)
    steps.add(step)
    return step
}

Thank you

Upvotes: 1

Views: 116

Answers (1)

Sweeper
Sweeper

Reputation: 271775

The init() is referring to the parameter init: CustomStep.() -> Unit:

constructor(init: CustomStep.() -> Unit): this(){
//  vvvv    ^^^^
    init()
}

You are simply calling what you passed in, on this. init needs a CustomStep as receiver after all. As with most situations when calling something on this, this can be omitted, which is what happens here. In the case of customStep, you passed in block.

val step = CustomStep(block)

block is this bit from main:

{
    name = "flang"
    anotherCommand = "derp"
    command = "1111"
}

Your alternative of CustomStep().apply(block) is the same too. Calling the secondary constructor that you declared will first call the parameterless primary constructor, as you have declared as : this(), and is required. This is the same as CustomStep(). Then both versions call block on this.

Upvotes: 1

Related Questions