Kotlin Generics: why is the supertype of "SomeActor : DataActor<SomeData>" "DataActor<out SomeData>" and not "DataActor<SomeData>"?

huangapple go评论59阅读模式
英文:

Kotlin Generics: why is the supertype of "SomeActor : DataActor<SomeData>" "DataActor<out SomeData>" and not "DataActor<SomeData>"?

问题

Line of Interest: 29: val actor: DataActor&lt;out Data&gt; = when ...
"out" 的添加使得调用以 Data 作为参数的函数变得不可能(例如第36行)。

使用此问题 question 我能够理解问题所在:
如果类型是 DataActor&lt;Data&gt;,则可以将任何 Data 添加到任何 Actor 中。例如,可以将 Text 添加到 NumberActor 中。因为我只将由 SomeActor 生成的数据用作 SomeActor 的消费品,我知道这样做是安全的。

但问题没有回答的是:如何以惯用方式实现我想要的功能?

我唯一想到的没有未经检查的转换的解决方案是在 when 子句中执行所有操作,但在实际应用中,这将导致大量的代码重复。

interface Data
class Text(var text: String) : Data
class Number(var number: Int) : Data

interface DataActor&lt;A: Data&gt; {
    fun add(element: A): Boolean;
    fun get(index: Int): A
}

class TextActor : DataActor&lt;Text&gt; {
    private val list = ArrayList&lt;Text&gt;()
    override fun add(element: Text) = list.add(element)
    override fun get(index: Int): Text = list[index]
}
class NumberActor : DataActor&lt;Number&gt; {
    private val list = ArrayList&lt;Number&gt;()
    override fun add(element: Number) = list.add(element)
    override fun get(index: Int): Number = list[index]
}
enum class Selector {
    Number,
    Text
}

fun main() {
    val selector = Selector.Number

    // 为什么它的类型是 DataActor&lt;out Data&gt; 而不是 DataActor&lt;Data&gt;?
    val actor = when(selector) {
        Selector.Number -&gt; NumberActor()
        Selector.Text -&gt; TextActor()
    } // as DataActor&lt;Data&gt; // 丑陋的解决方案

    val data = actor.get(0)
    // 由于需要 DataActor&lt;in Data&gt; 或 DataActor&lt;Data&gt;,所以这是不可能的
    actor.add(data)
}
英文:

I have build a minimal example to show my problem. I already know a ugly solution but I would like to find a better solution.

Line of Interest: 29: val actor: DataActor&lt;out Data&gt; = when ...
The addition of "out" makes it impossible to call functions that take Data as argument (e.g line 36).

With this question question I was able to understand what the problem is:
If the type would be DataActor&lt;Data&gt; It would be possible to add any Data to any Actor. E.g it would be possible to add Text to NumberActor. Because I only use data produced by SomeActor as consumable for SomeActor I know it save to do.

But what the question does not answer: What's the idiomatic way to impl. my desired functionality?

The only solution without unchecked casts I can come up with is to do all actions inside the when clause but in the real application this would lead to massive code duplication.

interface Data
class Text(var text: String) : Data
class Number(var number: Int) : Data

interface DataActor&lt;A: Data&gt; {
    fun add(element: A): Boolean;
    fun get(index: Int): A
}

class TextActor : DataActor&lt;Text&gt; {
    private val list = ArrayList&lt;Text&gt;()
    override fun add(element: Text) = list.add(element)
    override fun get(index: Int): Text = list[index]
}
class NumberActor : DataActor&lt;Number&gt; {
    private val list = ArrayList&lt;Number&gt;()
    override fun add(element: Number) = list.add(element)
    override fun get(index: Int): Number = list[index]
}
enum class Selector {
    Number,
    Text
}

fun main() {
    val selector = Selector.Number

    // Why is it from type DataActor&lt;out Data&gt; and not DataActor&lt;Data&gt;?
    val actor = when(selector) {
        Selector.Number -&gt; NumberActor()
        Selector.Text -&gt; TextActor()
    } // as DataActor&lt;Data&gt; // ugly solution

    val data = actor.get(0)
    // This is not possible as it needs DataActor&lt;in Data&gt; or DataActor&lt;Data&gt;
    actor.add(data)
}

答案1

得分: 1

I think you explained you already understand the problem. These two classes do not have the same invariant supertype. They can only have a common covariant supertype.

So to solve this, you need to break out the code that maintains invariance into its own generic function. Within the generic function, T is invariant. It doesn't care which T it is, so the code is safe.

fun main() {
    val selector = Selector.Number

    val actor = when (selector) {
        Selector.Number -> NumberActor()
        Selector.Text -> TextActor()
    }

    doWork(actor)
}

fun <T: Data> doWork(actor: DataActor<T>) {
    val data = actor.get(0)
    actor.add(data)
}

This of course is based on your very simple example. This solution may not be feasible for more complicated cases, like if you were trying to transfer items between distinct instances of these classes.

英文:

I think you explained you already understand the problem. These two classes do not have the same invariant supertype. They can only have a common covariant supertype.

So to solve this, you need to break out the code that maintains invariance into its own generic function. Within the generic function, T is invariant. It doesn't care which T it is, so the code is safe.

fun main() {
    val selector = Selector.Number

    val actor = when(selector) {
        Selector.Number -&gt; NumberActor()
        Selector.Text -&gt; TextActor()
    } 

    doWork(actor)
}

fun &lt;T: Data&gt; doWork(actor: DataActor&lt;T&gt;) {
    val data = actor.get(0)
    actor.add(data)
}

This of course is based on your very simple example. This solution may not be feasible for more complicated cases, like if you were trying to transfer items between distinct instances of these classes.

huangapple
  • 本文由 发表于 2023年4月6日 19:13:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/75948864.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定