【问题标题】:Existentials vs Covariance in ScalaScala中的存在与协方差
【发布时间】:2017-09-04 05:42:32
【问题描述】:

考虑下面的两个代码。他们实现了相同的目标:只有这样的A[T]-s 可以存储在Container 中,其中T 扩展C

然而,他们使用两种不同的方法来实现这一目标:

1) 存在主义

2) 协方差

我更喜欢第一个解决方案,因为这样A 仍然更简单。我有什么理由想要使用第二种解决方案(协方差)?

我对第二种解决方案的问题是它不自然,因为它不应该是A-s 的责任来描述我可以在 Container 中存储什么而不是什么,应该是Container的责任。第二种解决方案也更复杂,一旦我想开始对A 进行操作,然后我必须处理所有与协方差相关的东西。

使用第二种(更复杂、更不自然)的解决方案有什么好处?

object Existentials extends App {

  class A[T](var t:T)

  class C

  class C1 extends C

  class C2 extends C

  class Z

  class Container[T]{
    var t:T = _
  }

  val c=new Container[A[_<:C]]()
  c.t=new A(new C)
  //  c.t=new Z // not compile

  val r: A[_ <: C] = c.t

  println(r)
}

object Cov extends App{
  class A[+T](val t:T)

  class C

  class C1 extends C

  class C2 extends C

  class Z

  class Container[T]{
    var t:T = _
  }

  val c: Container[A[C]] =new Container[A[C]]()
  c.t=new A(new C)
  //c.t=new A(new Z) // not compile

  val r: A[C] = c.t

  println(r)
}

编辑(回应 Alexey 的回答):

评论: “我对第二种解决方案的问题是,从某种意义上说,描述我可以存储在 Container 中的内容和不能存储的内容不应该是 Container 的责任,这是不自然的。”

如果我有class A[T](var t:T),这意味着我只能在容器中存储A[T]-s 而不能(A[S] where S&lt;:T)在任何容器中。

但是,如果我有class A[+T](var t:T),那么我可以将A[S] 存储在S&lt;:T 的任何容器中。

因此,当声明A 是不变的或协变的时,我决定可以将哪种类型的 A[S] 存储在容器中(如上所示),此决定发生在 A 的声明中。

但是,我认为,这个决定应该在容器的声明中进行,因为它是特定于容器的,允许进入该容器的只有 A[T]-s 或 A[S] where @ 987654340@-s。

换句话说,更改A[T] 中的方差会产生全局影响,而将容器的类型参数从A[T] 更改为A[_&lt;:S] 会对容器本身产生明确的局部影响。所以这里的“变化应该有局部影响”的原则也有利于存在主义的解决方案。

【问题讨论】:

    标签: scala covariance existential-type


    【解决方案1】:

    在第一种情况下,A 更简单,但在第二种情况下,它的客户更简单。由于您通常在不止一个地方使用A,因此这通常是一个值得权衡的选择。你自己的代码演示一下:当你需要在第一种情况下(在两个地方)写A[_ &lt;: C]时,你可以在第二种情况下使用A[C]

    此外,在第一种情况下,您可以只写A[C],其中A[_ &lt;: C] 是真正需要的。假设你有一个方法

    def foo(x: A[C]): C = x.t
    

    现在你不能用y: A[C1] 调用foo(y),即使它是有意义的:y.t 确实有类型C

    当您的代码中发生这种情况时,可以修复它,但是第三方呢?

    当然,这也适用于标准库类型:如果像 MaybeList 这样的类型不是协变的,则所有获取/返回它们的方法的签名都必须更复杂,或者许多程序当前有效且完全合理会中断。

    描述我可以在 Container 中存储什么以及不能存储什么不应该是 A-s 的责任,这应该是 Container 的责任。

    差异与您可以在容器中存储的内容无关;它是关于何时A[B]A[C] 的子类型。这个论点有点像说你根本不应该有extends:否则class Apple extends Fruit 允许你在Container[Fruit] 中存储Apple,并决定这是Container 的责任。

    【讨论】:

    • 感谢您的回答。 “此外,如果你忘记在一种方法中使用正确的存在,任何其他调用它的地方也不能使用存在类型(没有不安全的未经检查的强制转换)。”你能举个简单的例子吗?我不太明白你的意思。
    • “在第一种情况下,A 更简单,但在第二种情况下,它的客户更简单。”对于客户来说,它更简单吗?没看懂,可以举个例子吗?
    猜你喜欢
    • 2016-10-16
    • 2016-08-24
    • 2012-06-29
    • 1970-01-01
    • 1970-01-01
    • 2015-06-09
    • 1970-01-01
    • 1970-01-01
    • 2021-01-21
    相关资源
    最近更新 更多