为什么Kotlin密封类不能实现子类型函数重载?

w8ntj3qf  于 2023-05-29  发布在  Kotlin
关注(0)|答案(3)|浏览(155)

假设我有一个用于服务器响应的密封类:

sealed class Response{
    class Success: Response() 
    class ErrorA: Response() 
    class ErrorB: Response() 
}

一个虚假的回答:

fun getResponse(): Response{
    val r = Random()
    return when (r.nextInt(3)) {
        0 -> { Response.Success() }
        1 -> { Response.ErrorA() }
        2 -> { Response.ErrorB() }
        else -> { throw IllegalStateException() }
    }
}

我想处理回应。我现在可以使用这样的东西:

fun handle(response: Response) = when (response) {
    is Response.Success -> { handle(response) }
    is Response.ErrorA -> { handle(response) }
    is Response.ErrorB -> { handle(response) }
}

然后编译器将确保处理所有情况。一个令人敬畏的功能!
为什么我不能做这样的事情:

class ResponseHandler(){

    fun handle(success: Response.Success) {}

    fun handle(error: Response.ErrorB) {}

    fun handle(error: Response.ErrorA) {}
}

然后打电话

ResponseHandler().handle(response)

这实现了同样的事情,但不编译,我的问题是:就像编译器在运行时确保所有情况都在when语句中处理一样,为什么同样的逻辑不能应用于方法重载?
任何信息或推荐进一步阅读将是非常有帮助的。谢谢

exdqitrt

exdqitrt1#

原则上,这是可以做到的(基本上是通过自动生成handle(response: Response) = when ...方法)。但我觉得这不太可能。Kotlin中的重载基本上与Java/Scala/其他JVM语言中的重载相同,并且引入如此小的好处的主要差异看起来不是一个好主意(当然这不适用于Kotlin特定的when)。
如果需要,可以在ResponseHandler中定义相同的fun handle(response: Response)(并将其他handle方法设置为open,这样实际上就很有用了)。

von4xj4u

von4xj4u2#

这个问题可以分解为这个简化的例子:

fun calc(i: Int) = i * 2
fun calc(d: Double) = d * 2

fun main(args: Array<String>) {
    val i: Number = 5
    calc(i)
}

您有两个专门的方法,分别接受IntDouble。您的值的类型为NumberIntDouble超类型)。虽然i显然是一个整数,但变量的类型为Number,它不能作为calc(i: Int)calc(d: Double)的参数。
在您的例子中,您得到一个Response,并希望调用一个重载方法,这些方法都不直接接受Response

ugmeyewa

ugmeyewa3#

对于任何对此感兴趣的人,我花了很长时间试图找到一种具有我在问题中描述的动态行为的语言,但同时维护一个真实的的类型系统。问了这个问题两年后,我遇到了朱莉娅,并了解到描述我试图实现的目标的短语是multiple dispatch
我的问题源于sealed class提供的功能。这本质上是“求和类型”,允许编译器保证每个子类型都包含在when表达式中。它确实是一个编译时特性。
多重分派是实现以下功能的一个功能:当方法被调用时,语言在运行时确定哪个函数最适当地匹配类型,而不是在编译时确定要调用的函数。换句话说,它运行与参数最匹配的函数。
Kotlin没有多重分派,这可以通过以下方式来证明:

object Dispatcher {
    fun add(a: Int, b: Int) {
       // ...
    }

    fun add(a: Float, b: Float) {
       // ...
    }

    fun add(a: Float, b: Int) {
       // ...
    }
}

fun main(){
    val numbers = listOf<Number>(1, 2.0)
    Dispatcher.add(numbers[0], numbers[1])
}

尝试运行此命令会导致以下编译时错误:

None of the following functions can be called with the arguments supplied:
public final fun add(a: Float, b: Float): Unit defined in Dispatcher
public final fun add(a: Int, b: Float): Unit defined in Dispatcher
public final fun add(a: Int, b: Int): Unit defined in Dispatcher

尽管numbers[0]numbers[1]的值分别具有IntFloat类型,但编译器在编译时无法知道这一点,因此不会调用add(a: Int, b: Float)
与支持多分派的语言相比,这是一个很大的区别。Julia就是这样一种语言,实际上它使多重分派成为一个核心特性。上面的例子可以在Julia中这样实现:

julia> function add(a::Int, b::Int)
           println("add Int to Int")
       end

julia> function add(a::Float64, b::Float64)
           println("add Float to Float")
       end

julia> function add(a::Int, b::Float64)
           println("add Int to Float")
       end

然后调用add看起来像这样:

julia> numbers = Number[1, 2.0]
2-element Vector{Number}:
 1
 2.0

julia> add(numbers[1], numbers[2])
add Int to Float

Julia确定在运行时调用哪个方法。
与所有编程语言的设计特性一样,每种选择都有一组权衡,通常适用于不同的上下文。Kotlin和Java能够有广泛的工具集成,因为类型可以在编译时确定。Julia能够成为一种具有更高表现力的动态语言,但牺牲了一些静态分析。

相关问题