因为具有表示类型的特征是自引用的,所以声明变量包含该特征的实例有点困难。在这个例子中,我只是声明一个变量包含一个特征的实例,声明一个函数接受并返回该特征的实例,并用该变量调用该函数:
trait Foo[+A <: Foo[A]]
case class Bar() extends Foo[Bar]
case class Grill() extends Foo[Grill]
// Store a generic instance of Foo
val b: Foo[_] = if(true) {
Bar()
} else {
Grill()
}
// Declare a function that take any Foo and returns a Foo of the same type
// that "in" has in the calling context
def echoFoo[A <: Foo[A]](in: A): A = in
// Call said function
val echo = echoFoo(b)
失败并显示错误:
inferred type arguments [this.Foo[_$1]] do not conform to method
echoFoo's type parameter bounds [A <: this.Foo[A]]
val echo = echoFoo(b)
^
现在,这是有道理的,因为[_]
就像Any
(我不完全理解的方式)。它可能需要的是Foo[Foo[_]]
,因此类型参数符合A <: Foo[A]
的范围。但是现在有一个内部Foo
具有不符合的类型参数,表明解决方案类似于Foo[Foo[Foo[Foo[...
,这显然是不正确的。
所以我的问题可能可以归结为:“此变量是否包含任何合法Foo
”的Scala语法是什么?
答案 0 :(得分:2)
像这样的自引用类型参数有点问题,因为它们不是声音。例如,可以定义如下类型:
case class BeerGarden extends Foo[Grill]
如你所见,A&lt;:Foo [A]的界限不够紧。在这种情况下我更喜欢使用蛋糕模式和抽象类型成员:
trait FooModule {
type Foo <: FooLike
def apply(): Foo
trait FooLike {
def echo: Foo
}
}
现在,您可以递归且安全地使用Foo类型:
object Foos {
def echo(foo: FooModule#Foo) = foo.echo
}
显然,对于您可能希望用这些类型解决的所有问题,这不是一个理想的解决方案,但重要的观察是FooLike是一个可扩展的特性,因此您可以随时继续优化FooLike以添加成员您需要,而不违反类型成员要强制执行的约束。我发现,在我想要表示的类型集合未关闭的每个真实案例中,这都是人们可以做到的最好的情况。重要的是FooModule在实例构造函数的和类型上进行抽象,同时强制执行“自我类型”。你不能抽象而不是抽象而不是抽象。
有关此类事情的一些其他信息(以及我自己早期与递归类型斗争的记录)可在此处获取:
答案 1 :(得分:0)
虽然我同意存在传播泛型的问题,但当你遇到这个问题时,你应该在屏幕上看到一个很大的警告,因为它通常表明设计不好。这些是关于该主题的一般性建议。
如果您使用泛型,那么type参数就是有原因的。它允许您通过传入或接收类型A的参数以类型安全的方式与Foo [A]交互,并允许您对A进行约束。如果丢失了类型信息,则会丢失类型安全性,并且如果您不再需要通用类,则无需编写泛型类:您可以将所有签名更改为Any并进行模式匹配。
在大多数情况下,通过使用“类型类”
最后,类型投影(FooModule#Foo)几乎没有应用程序,您可能希望查看与路径相关的类型。然而,这些也几乎没有应用。