scala - 泛型中的任何与下划线
Posted
技术标签:
【中文标题】scala - 泛型中的任何与下划线【英文标题】:scala - Any vs underscore in generics 【发布时间】:2013-03-03 14:07:32 【问题描述】:Scala 中以下泛型定义有什么不同:
class Foo[T <: List[_]]
和
class Bar[T <: List[Any]]
我的直觉告诉我它们大致相同,但后者更明确。我发现前者可以编译但后者不能编译的情况,但我无法确定确切的区别。
谢谢!
编辑:
我可以再加入一个吗?
class Baz[T <: List[_ <: Any]]
【问题讨论】:
将类型限制为<: Any
永远不会改变任何东西。 Scala 中的每种类型都是<: Any
。
【参考方案1】:
好的,我想我应该接受它,而不是仅仅发布 cmets。抱歉,这会很长,如果您希望 TL;DR 跳到最后。
正如 Randall Schulz 所说,这里的 _
是存在类型的简写。即,
class Foo[T <: List[_]]
是
的简写class Foo[T <: List[Z] forSome type Z ]
请注意,与 Randall Shulz 的回答所提到的相反(完全披露:我在这篇文章的早期版本中也弄错了,感谢 Jesper Nordenberg 指出)这与以下内容不同:
class Foo[T <: List[Z]] forSome type Z
也不等同于:
class Foo[T <: List[Z forSome type Z ]]
当心,很容易出错(正如我之前的傻瓜所示):Randall Shulz 的回答所引用的文章的作者自己弄错了(参见 cmets),后来修复了它。我对这篇文章的主要问题是,在显示的示例中,存在主义的使用本应使我们免于打字问题,但事实并非如此。去检查代码,并尝试编译compileAndRun(helloWorldVM("Test"))
或compileAndRun(intVM(42))
。是的,不编译。在A
中简单地使compileAndRun
泛型将使代码编译,并且它会简单得多。
简而言之,这可能不是了解存在主义及其好处的最佳文章(作者本人在评论中承认该文章“需要整理”)。
所以我宁愿推荐阅读这篇文章:http://www.artima.com/scalazine/articles/scalas_type_system.html,尤其是名为“Existential types”和“Variance in Java and Scala”的部分。
您应该从本文中得到的重要一点是,在处理非协变类型时,存在函数很有用(除了能够处理泛型 Java 类之外)。 这是一个例子。
case class Greets[T]( private val name: T )
def hello() println("Hello " + name)
def getName: T = name
这个类是通用的(注意它是不变的),但我们可以看到hello
真的没有使用类型参数(不像getName
),所以如果我得到一个@ 的实例987654336@我应该总是可以打电话给它,不管T
是什么。如果我想定义一个采用Greets
实例并只调用其hello
方法的方法,我可以试试这个:
def sayHi1( g: Greets[T] ) g.hello() // Does not compile
果然,这不会编译,因为 T
不知从何而来。
好吧,让我们将方法设为泛型:
def sayHi2[T]( g: Greets[T] ) g.hello()
sayHi2( Greets("John"))
sayHi2( Greets('Jack))
太好了,这行得通。我们也可以在这里使用existentials:
def sayHi3( g: Greets[_] ) g.hello()
sayHi3( Greets("John"))
sayHi3( Greets('Jack))
也可以。所以总而言之,在类型参数(如sayHi2
)上使用存在(如sayHi3
)并没有真正的好处。
但是,如果Greets
将自身作为另一个泛型类的类型参数出现,则情况会发生变化。举例来说,我们想在一个列表中存储多个Greets
(具有不同的T
)实例。让我们试试吧:
val greets1: Greets[String] = Greets("John")
val greets2: Greets[Symbol] = Greets('Jack)
val greetsList1: List[Greets[Any]] = List( greets1, greets2 ) // Does not compile
最后一行没有编译,因为Greets
是不变的,所以Greets[String]
和Greets[Symbol]
不能被视为Greets[Any]
,即使String
和Symbol
都扩展了Any
。
好的,让我们尝试使用简写符号_
:
val greetsList2: List[Greets[_]] = List( greets1, greets2 ) // Compiles fine, yeah
这编译得很好,你可以按预期做:
greetsSet foreach (_.hello)
现在,请记住,我们首先遇到类型检查问题的原因是因为Greets
是不变的。如果将其转换为协变类 (class Greets[+T]
),那么一切都会开箱即用,我们将永远不需要存在主义。
总而言之,existentials 对于处理泛型不变类很有用,但是如果泛型类不需要将自身作为另一个泛型类的类型参数出现,那么您可能不需要存在主义并简单地添加您的方法的类型参数将起作用
现在回到(终于,我知道了!)您的具体问题,关于
class Foo[T <: List[_]]
因为List
是协变的,所以这与刚才说的所有意图和目的相同:
class Foo[T <: List[Any]]
所以在这种情况下,使用任何一种表示法实际上只是风格问题。
但是,如果将 List
替换为 Set
,情况就会发生变化:
class Foo[T <: Set[_]]
Set
是不变的,因此我们的情况与我的示例中的 Greets
类相同。因此,上面的内容确实与
class Foo[T <: Set[Any]]
【讨论】:
不,class Foo[T <: List[_]]
是 class Foo[T <: List[Z] forSome type Z ]
的简写。同样,List[Greets[_]]
是 List[Greets[Z] forSome type Z ]
的简写(而不是 List[Greets[Z]] forSome type Z
)。
哎呀,真傻!谢谢,我已经解决了这个问题。有趣的是,我的第一个想法是查看 David R MacIver (drmaciver.com/2008/03/existential-types-in-scala) 的文章,该文章准确地谈到了存在主义的简写,并警告它们不直观的去糖化。问题是他自己似乎搞错了。实际上,发生的事情是脱糖的方式在他的文章之后不久发生了变化(在 scala 2.7.1 中,请参阅scala-lang.org/node/43#2.8.0 的更改日志)。我猜这种变化会引起混乱。
嗯,很容易混淆存在类型语法的含义。至少目前的脱糖是最合乎逻辑的恕我直言。
最后一个]
去哪里class Foo[T <: List[Z forSome type Z ]
?
是的,最后缺少]
。那应该是class Foo[T <: List[Z forSome type Z ]]
。现已修复,谢谢。【参考方案2】:
当代码不需要知道类型是什么或对其进行约束时,前者是存在类型的简写:
class Foo[T <: List[Z forSome type Z ]]
此表单表示class Foo
不知道List
的元素类型,而不是您的第二个表单,它明确表示List
的元素类型是Any
。
查看关于 Scala 中存在类型的简短说明 blog article(编辑:此链接现已失效,快照可在 archive.org 获得)
【讨论】:
当你解释什么是存在时,我认为问题不是一般的存在,而是T[_]
(这是存在使用的特殊情况)和@之间是否有任何实际可观察到的区别987654329@?
当然有。我提到的博客有一个很好的说明。
我希望 您的 回答提到协方差对于 T[_]
和 T[Any]
之间的差异的重要性,因为它是问题“为什么甚至使用T[_]
超过T[Any]
"。此外,Sean Connolly 在他的问题中明确提到了List[_]
。鉴于List
实际上是协变的,人们可能想知道在这种情况下List[_]
和List[Any]
之间是否真的存在差异。
如果T是协变的并且它的类型参数有Any
作为它的上限,那么T[_]
和T[Any]
之间似乎没有任何区别.但是,如果你有T[+A <: B]
,编译器就不能从T[_]
推断出T[_ <: B]
。
@Jesper Nordenberg:实际上,在 scala pre-2.8 中,这个推断是有效的,它在 scala 2.8 中被“破坏”了。事实上,这是故意的,因为显然这与类型系统的其余部分表现不佳,并且不一致(虽然我不记得细节)。以上是关于scala - 泛型中的任何与下划线的主要内容,如果未能解决你的问题,请参考以下文章