'Kotlin DSL - Generic lambda with multiple receivers

I'm creating a DSL for my project and I really want be able to use fields/methods from multiple receivers without nesting them:

For example:

class Foo {
    val fooField = "Foo field"
}

class Bar {
    val barField = "Bar field"
}

fun main() {

    val foo = Foo()
    val bar = Bar()

    (foo..bar) {
        println(fooField)
        println(barField)
    }

}

Instead of:

class Foo {
    val fooField = "Foo field"

    operator fun invoke(block: Foo.() -> Unit) {
        block(this)
    }
}

class Bar {
    val barField = "Bar field"

    operator fun invoke(block: Bar.() -> Unit) {
        block(this)
    }
}

fun main() {

    val foo = Foo()
    val bar = Bar()

    foo {
        bar {
            println(fooField)
            println(barField)    
        }
    }
}

I have thought about something like:

class Buzz<T1, T2>(
    val t1: T1,
    val t2: T2
) {

    operator fun invoke(function: /* T1 + T2*/ () -> Unit) { // <--- ???
        function(/* t1 + t2*/)
    }
}

operator fun <T, R> T.rangeTo(r: R): Buzz<T, R> {
    return Buzz(this, r)
}

But can I combine two receivers somehow without boilerplate?



Solution 1:[1]

You can't do that simply because a single variable can't hold reference of two objects at the same time.

But similar looking solution could be to use a pair, it could relate* with your problem:

val foo = Foo()
val bar = Bar()

Pair(foo,bar).apply {         // this: Pair<Foo, Bar>
    println(first.fooField)
    println(second.barField)
}

Note*: This is just a suggestion as it does remove boilerplate by nested lambdas, but in other hand does not provide a solution with lambda with multiple receivers

Solution 2:[2]

Yes, you can implement this using delegation pattern

interface Foo {
    val fooField get() = "Foo field"
}

class FooImpl: Foo

interface Bar {
    val barField get() = "Bar field"
}

class BarImpl: Bar

class FooBar(
    val foo: Foo,
    val bar: Bar
): Foo by foo, Bar by bar {
    operator fun invoke(block: FooBar.() -> Unit) {
        block(this)
    }
}

operator fun Foo.rangeTo(bar: Bar): FooBar = FooBar(this, bar)

fun main() {
    val foo = FooImpl()
    val bar = BarImpl()

    (foo..bar) {
        println(fooField)
        println(barField)
    }

}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Animesh Sahu
Solution 2 Semyon