Search code examples
kotlinextension-methods

Invoke receiver function declared in interface


I'm trying to create an easy-to-use html generator for a personal project. I thought I would use extension functions to be able to generate an html programmatically using something like this:

html {
    head {
        title("Site title")
    }
    body {
        div {
            // stuff in div
        }
    }
}

For that I declared an interface:

fun interface TagBlock {
    operator fun Tag.invoke()
}

Where Tag would be the class designating the specific tags, like html, body, div etc:

class Tag(val name: String)

I now tried to create a function which accepts the earlier mentioned interface and returns a tag:

fun html(block: TagBlock): Tag {
    val html = Tag("html")
    // invoke `block` with `html`
    return html
}

I'm stuck on how to invoke the provided parameter block. The following all don't work:

block(html) // Unresolved reference
block.invoke(html) // unresolved reference
html.block() // Unresolved reference: block

Where am I doing something wrong?


Solution

  • The invoke() operator you're declaring has 2 receivers:

    • the dispatch receiver TagBlock
    • the explicit receiver Tag

    You need to provide the dispatch receiver in the context of your call for it to work. You can do this with the library function with():

    fun html(block: TagBlock): Tag {
        val html = Tag("html")
        with(block) {
            html.invoke()
        }
        return html
    }
    

    This may or may not be the usage experience you were looking for, though.

    A more idiomatic approach in Kotlin would be to just take a function type as input:

    fun html(block: Tag.() -> Unit): Tag {
        val html = Tag("html")
        html.block()
        return html
    }