L. Blanc
L. Blanc

Reputation: 2310

Kotlin: higher-order function taking vararg lamba-with-receiver, where the receiver takes arguments

I am trying to wrap a hierarchy of Java builders in a Kotlin type-safe builder. The hierarchy consists of the following builders (and their targets):

In Java, FigureBuilder has one method that takes a Layout, and another that take n traces, using a varargs method called addTraces():

addTraces(Trace... traces)

The assembly process in Java is basically

Figure f = Figure.builder()
   .layout(
       Layout.builder()
           .title("title")
           .build())
   .addTraces(
       ScatterTrace.builder()
           .name("my series")
           .build())
   .build();       

In Kotlin, I have code that creates the figure builder and the layout builder, but I am stuck on the trace builder. My code so far looks like this:

 val figure = figure {
            layout {title("Wins vs BA")}

            addTraces(
                ScatterTrace.builder(x, y)
                    .name("Batting avg.").build()
            )
        }


fun figure(c: FigureBuilder.() -> Unit) : Figure {
    val builder = Figure.builder()
    c(builder)
    return builder.build()
}

fun FigureBuilder.layout(c: Layout.LayoutBuilder.() -> Unit) {
    layout(Layout.builder().apply(c).build())
}

// won't compile: ScatterTrace.builder() requires 2 args
fun FigureBuilder.traces(vararg c: ScatterTrace.ScatterBuilder.() -> Unit) {
    c.forEach {
        val builder = ScatterTrace.builder()
        it(builder)
        addTraces(builder.build())
    }
}

I'm not at all sure the last function will work if I can get it to compile, but the immediate blocking issue is that ScatterTrace.builder() takes two arguments and I cannot figure out how to pass them into the lambda.

Thanks very much

Upvotes: 0

Views: 1550

Answers (2)

Brandon McAnsh
Brandon McAnsh

Reputation: 1031

fun FigureBuilder.traces(vararg c: ScatterTrace.ScatterBuilder.() -> Unit) {
    addTraces(
            *c.map {
                val builder = ScatterTrace.builder()
                builder.build()
            }.toTypedArray()
    )
}

should do what you are looking for with your vararg requirement.

Upvotes: 1

Andrei Tanana
Andrei Tanana

Reputation: 8442

It's strange that in Java you can create ScatterTrace.builder without arguments but in Kotlin you need two arguments to construct it. Maybe it will be better to apply traces one by one?

fun FigureBuilder.traces(x: Int, y: Int, c: ScatterTrace.ScatterBuilder.() -> Unit) {
    val builder = ScatterTrace.builder(x, y)
    c(builder)
    addTraces(builder.build())
}

val figure = figure {
    layout { title("Wins vs BA") }

    addTraces(
        trace(x, y) { name("Batting avg.") },
        trace(x, y) { name("Batting avg. 2") },
        trace(x, y) { name("Batting avg. 3") }
    )
}

Upvotes: 1

Related Questions