【问题标题】:Type Boundary "Stickyness" on Wildcards通配符的类型边界“粘性”
【发布时间】:2012-08-14 07:03:31
【问题描述】:

我在 scala 论坛上看到了这个post

这里是一个回顾:

我刚刚意识到 Scala 编译器似乎没有在通配符上“继承”类型边界:

scala> class Foo { def foo = 42 }
defined class Foo
scala> class Bar[A <: Foo](val a: A)
defined class Bar
scala> def bar(x: Bar[_]) = x.a.foo
:7: error: value foo is not a member of Any

我希望方法 bar 的参数仍然是 Foo 的上限,即使它的确切类型参数在方法中并不重要。 这种行为是否有特定原因?

然后讨论进入规范解释争议:)

解释?

最终海报提出了这个解释:

但是,如果编译器为 Bar[_] 执行此操作,出于一致性原因,它也必须为 Bar[A] 执行此操作。然而,后者会产生一些奇怪的后果。 def bar[A](x: Bar[A], y: Bob[A]) 例如,突然必须携带一个为 Bob 绑定的隐式类型。如果 Bob 有自己的类型绑定,事情会非常混乱。

我不明白是因为

def bar[A](x: Bar[A])

单独无法编译,因为 Bar 类型参数是有界的。

无论如何,我相信以下内容完全有道理:

def bar(x: Bar[_], y : Bob[_])

解决方法

他们建议的解决方法是:

def bar(x: Bar[_ <: Foo]) = x.a.foo

除了不干燥之外,这会让事情变得困难:

让我们考虑一棵树

abstract class Tree[T <: Tree[T]] { val subTree : List[T] }

现在你将如何定义递归遍历树的函数(显然是在 Tree 类之外定义的):

def size( tree : Tree[_] ) = tree.subTree.size + tree.subTree.map(size(_)).sum

显然不行,因为subTree会变成List[Any],所以我们需要一个类型参数:

def size[T <: Tree[T]]( tree : T ) = ...

更丑:

class OwnATree( val myTree : Tree[_] ){}

应该变成

class OwnATree[T <: Tree[T]]( val myTree : T ){}

等等等等……

我相信某处有问题:)

【问题讨论】:

  • 您要求更改语言规范。不知道能给出什么答案?是的,您需要使用存在类型或泛型方法。您可以希望 Scala 3 统一类型参数和类型成员 :)
  • @0__:我认为这里有一个足够清楚的问题(如何在没有类型参数的情况下编写size 等)。我也不确定在这种情况下统一类型参数和类型成员会有什么帮助。
  • 是的,这个问题有点含糊。一方面,我一直在寻找规范中没有的解释——可能有一个原因使它无法实施。另一方面,寻找类型参数的替代方案 - @TravisBrown 回答了。
  • @Travis :在this thread 中,Martin Odersky 勾勒出统一的概念。 “那么大的简化是:(1)任何类型都可以在没有参数的情况下编写。[...]”目前尚不清楚这是否意味着我们确实得到了正确的界限(也许不是)......但是还有:“其他类型系统范围更窄的想法是引入类型的最小上限和最大下限作为类型构造函数。这将避免我们今天有时在代码库中看到的计算 lub 类型的爆炸式增长。 – 所以我希望...

标签: scala


【解决方案1】:

使用sizeOwnATree 完成您想要的最简单的方法是使用存在类型:

def size(tree: Tree[T] forSome { type T <: Tree[T] }): Int =
  tree.subTree.size + tree.subTree.map(size(_)).sum

还有:

class OwnATree(val myTree: Tree[T] forSome { type T <: Tree[T] })

您的Tree[_] 版本实际上也使用了存在类型——它们只是包裹在一些语法糖中。换句话说,

def size(tree: Tree[_]): Int = ...

只是语法糖:

def size(tree: Tree[T] forSome { type T }): Int = ...

您不能将所需的约束添加到下划线版本,但可以添加到去糖版本。

【讨论】:

  • 啊,是的!我忘记了那个语法:) 感谢您指出。现在我的代码看起来更好了,但我仍然想知道为什么我们需要那些杂乱无章的东西。编译器不应该像原始海报所说的那样“跨越类型边界”吗?
  • 请注意,您还可以将其定义为 type alias 任何地方,如果您需要编写数十次,这可能很有用:type AnyTree = Tree[T] forSome { type T &lt;: Tree[T] } 然后def size(tree: AnyTree) ...
  • Nice :) 我正在这样使用它,也许有更好的方法:object Tree { type Any = ... } 现在我遇到了各种各样的问题...compiler crashinference bug?跨度>
  • 更不用说IDE(在这种情况下是Idea)完全丢失了:)
猜你喜欢
  • 1970-01-01
  • 2015-08-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-21
  • 2014-03-08
  • 1970-01-01
相关资源
最近更新 更多