【问题标题】:Covariant Trees in ScalaScala中的协变树
【发布时间】:2015-04-16 10:32:01
【问题描述】:

查看scalaz.Tree[A],它在 A 中是不变的。我正在寻找一种多路树,我可以将层次结构的值转储到其中

例如如果我的 ADT 为

trait MyThing
case object Thing1 extends MyThing
case object Thing2 extends MyThing

我想要一棵 MyThings 的树,如果不对 scalaz 中的 MyThing 进行强制转换,我就无法使用它;

import scalaz.{Scalaz, Tree}
import Scalaz._
val tree = Thing1.asInstanceOf[MyThing].
               node(Thing2.asInstanceOf[MyThing].leaf)

这有点痛苦。

  • 是否有协变 [+A] 版本的 Tree?
  • 首先为什么 Tree 是不变的?

【问题讨论】:

  • 顺便说一句,使用类型归属 (Thing1: MyThing) 比使用强制类型转换更安全;如果进行了更改,使得Thing1 和MyThing 之间不再存在关系,则案例将编译但在运行时失败,而归属甚至无法编译。
  • github.com/scalaz/scalaz/pull/383 是关于在 Scalaz 7.1 中删除方差注释的拉取请求;特别是,请参阅链接的 PR #328。而且我还没有检查它是否合适/可能,但与scalaz.IList#widen 使用的相同技术可能适用于此。
  • 我建议从相反的方向解决问题——使用静态类型为 MyThing 的 ADT 构造函数。
  • 感谢 cmets。 @Huw - 我怎么能在Tree 上使用widen?我猜想用扩大 ala IList 风格拉皮条def widen[B](implicit ev: A <~< B): Tree[B] = ev.subst[({type λ[-α] = Tree[α @uncheckedVariance] <~< Tree[B]})#λ](refl)(this)
  • 是的,就是这样。强烈推荐 Travis 的建议!

标签: scala data-structures tree scalaz


【解决方案1】:

首先,我想赞同 Huw 的建议,即您使用类型归属而不是使用 asInstanceOf 向下转换。正如 Huw 所说,如果类型层次结构中的某些更改导致强制转换无效,则使用类型归属将在编译时而不是运行时失败。对于 any 向上转换,避免 asInstanceOf 也是一个很好的做法。您可以使用 asInstanceOf 进行向上转换或向下转换,但仅将其用于向下转换可以轻松识别代码中的不安全转换。

回答你的两个问题——不,Scalaz 中没有协变树类型,原因在上面由 Huw 链接的pull request 中详细讨论。起初,这似乎是一个巨大的不便,但在 Scalaz 中避免使用非不变结构的决定与类似的设计决策有关——避免在 ADT 中使用子类型——这使得不变树等变得不那么痛苦。

在其他对 ADT 有良好支持的语言中(例如 Haskell 和 OCaml),ADT 的叶子不是 ADT 类型的子类型,而且 Scala 有点不寻常的基于子类型的实现会使类型推断变得混乱。下面是这个问题的一个常见例子:

scala> List(1, 2, 3).foldLeft(None)((_, i) => Some(i))
<console>:14: error: type mismatch;
 found   : Some[Int]
 required: None.type
              List(1, 2, 3).foldLeft(None)((_, i) => Some(i))
                                                     ^

因为累加器的类型是从foldLeft的第一个参数推断出来的,所以它最终是None.type,这几乎没有用。您必须提供类型归属(或 foldLeft 的显式类型参数),这可能非常不方便。

Scalaz 尝试通过促进使用不返回 ADT 叶的最特定子类型的 ADT 构造函数来解决此问题。例如,它包括用于返回 Option[A] 的 Option 的 none[A] 和 some[A](a: A) 构造函数。

(有关这些问题的更多讨论,请参阅我的回答here和this related question)。

在您的情况下,实现这种方法可能就像编写以下代码一样简单:

val thing1: MyThing = Thing1
val thing2: MyThing = Thing2

这允许你写thing1.node(thing2.leaf)。如果您想在这条道路上走得更远,我强烈建议您将Argonaut's Json ADT 视为淡化子类型作用的 ADT 设计的一个很好的例子。

【讨论】:

    猜你喜欢
    • 2015-02-09
    • 1970-01-01
    • 2020-05-02
    • 1970-01-01
    • 1970-01-01
    • 2020-01-25
    • 2015-04-01
    • 2013-11-29
    • 1970-01-01
    相关资源
    最近更新 更多