【问题标题】:Invoke receiver function declared in interface调用接口中声明的接收器函数
【发布时间】:2021-05-19 14:36:51
【问题描述】:

我正在尝试为个人项目创建一个易于使用的 html 生成器。我想我会使用扩展函数来以编程方式生成一个 html,如下所示:

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

为此我声明了一个接口:

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

其中Tag 是指定特定标签的类,例如htmlbodydiv 等:

class Tag(val name: String)

我现在尝试创建一个接受前面提到的接口并返回标签的函数:

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

我不知道如何调用提供的参数block。以下都不起作用:

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

我哪里做错了?

【问题讨论】:

    标签: kotlin extension-methods


    【解决方案1】:

    您声明的 invoke() 运算符有 2 个接收者:

    • 调度接收者TagBlock
    • 显式接收者Tag

    您需要在调用的上下文中提供调度接收器才能使其工作。您可以使用库函数with()

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

    不过,这可能是也可能不是您想要的使用体验。

    在 Kotlin 中更惯用的方法是将函数类型作为输入:

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

    【讨论】:

    • 非常感谢!我首先采用惯用的方法,但后来我不得不引入Tag 子类,所以我尝试了typealias,但遗憾的是它不支持类型约束,所以我使用自定义interface 来减少冗余整个项目。但是当我读到这篇文章时,也许我会回到只有一个函数类型的“更简单”的版本。 (我知道很多
    • @Lino 和 Tag 子类,使用函数类型的方法仍然有效,因为该函数可以将 Tag 的子类作为接收者(如果您想要更多类型约束)。或者,如果这还不够,您甚至可以想象使用泛型类型作为接收器。我可能不了解您正在尝试做的事情的全貌:)
    • 我现在采用惯用的方法,需要更多的写作,但它满足了我的需求。子类的情况是为这些函数的用户引入更多约束。 IE。 html 标签在我的情况下只允许 headbody 标签,body 不应该出现在另一个 body-标签内,所以有了这些通用约束,我将能够只允许特定标签我会选。另一个例子是title 标签,它只在head 标签内有效。我希望这能澄清一点:)
    猜你喜欢
    • 2017-05-10
    • 2015-02-15
    • 1970-01-01
    • 2018-01-10
    • 1970-01-01
    • 2013-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多