如何限制 Kotlin 扩展函数的参数与扩展类型相同?

19
我想在泛型类型T上编写一个扩展方法,其中匹配的类型限制了方法参数。
我希望这个能够编译通过:
"Hello".thing("world")

但是这个不行,因为42不是一个字符串:

"Hello".thing(42)

这个定义不可行,因为 T 可以被 Any 满足。

fun <T> T.thing(p: T) {}
3个回答

13

正如@Alexander Udalov所提到的那样,直接做是不可能的,但有一个变通方法,你可以像这样在另一个类型上定义扩展方法:

data class Wrapper<T>(val value: T)

val <T> T.ext: Wrapper<T> get() = Wrapper(this)

fun <T> Wrapper<T>.thing(p: T) {
    println("value = $value, param = $p")
}

通过上述操作,以下内容可以编译:

"abc".ext.thing("A")

但是下一个失败了。
"abc".ext.thing(2)

使用:

Kotlin: Type inference failed: Cannot infer type parameter T in fun <T> Wrapper<T>.thing(p: T): Unit
None of the following substitutions
receiver: Wrapper<String>  arguments: (String)
receiver: Wrapper<Int>  arguments: (Int)
can be applied to
receiver: Wrapper<String>  arguments: (Int)

正如 @hotkey 所建议的那样,使用以下扩展属性似乎可以避免需要显式 Wrapper 类型:

val <T> T.thing: (T) -> Any? get() = { println("extension body") }

然后使用它作为"abc".thing("A"),但是它仍然失败了。令人惊讶的是,以下代码可以编译通过:"abc".thing.invoke("A")


1
可以定义一个扩展属性并将invoke()函数添加到Wrapper中,使得这个解决方法在语法上与函数调用完全相同,但是编译器似乎存在一个错误,阻止我这样做。 - hotkey
1
有趣的是,即使是 ("abc".foo)("abc") 也可以工作。 :D - hotkey
我认为鉴于这个bug,这将被接受,但还是感谢@hotkey的工作。 - Duncan McGregor
在实践中,“Wrapper”甚至不需要捕获参数,只需返回并定义.get()即可。 - Duncan McGregor
在 Kotlin 1.4 中,这不再起作用。它只会编译。 - Nikola Mihajlović

10
据我所知,Kotlin 1.0 中不可能实现此功能。跟这个用例类似的问题在问题追踪器中被提出了几个(firstsecond),第一个问题中提出的解决方案将来也可能有助于解决这个问题。

5

改进@miensol的解决方法,并使其在视觉上与函数调用完全相同:

val <T> T.foo: (T) -> SomeType get() = { other -> ... }

这是一个扩展属性,提供了一个lambda表达式,可以立即使用相同类型T的参数进行调用,例如:
"abc".foo(1) // Fail
"abc".foo("def") // OK

很遗憾,编译器存在漏洞,阻止您编写"abc".thing("abc"),但是"abc".thing.invoke("abc")("abc".thing)("abc)都可以很好地工作并过滤出非字符串的调用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接