Lino
Lino

Reputation: 19926

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?

Upvotes: 2

Views: 275

Answers (1)

Joffrey
Joffrey

Reputation: 37710

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
}

Upvotes: 3

Related Questions