1

因为具有表示类型的特征是自引用的,所以声明一个变量包含该特征的实例有点困难。在这个例子中,我简单地声明一个变量包含一个 trait 的实例,声明一个函数接受并返回该 trait 的实例,然后使用该变量调用该函数:

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语法是什么?

4

2 回答 2

2

像这样的自引用类型参数有点问题,因为它们不正确。例如,可以定义如下类型:

case class BeerGarden extends Foo[Grill]

如您所见, A <: 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 对类型实例构造函数都进行了抽象,同时强制执行“自类型”。你不能抽象一个而不抽象另一个。

有关此类事情的一些附加信息(以及我自己早期与递归类型斗争的一些记录)可在此处获得:

https://issues.scala-lang.org/browse/SI-2385

于 2012-07-23T19:34:42.830 回答
0

虽然我同意存在传播泛型的问题,但当你遇到这个问题时,你应该会在屏幕上看到一个很大的警告,因为它通常表示设计不好。这些是关于该主题的一般建议。

  • 如果您使用泛型,则类型参数的存在是有原因的。它允许您通过传入或接收 A 类型的参数以类型安全的方式与 Foo[A] 进行交互,并允许您对 A 施加约束。如果您丢失类型信息,您将失去类型安全,并且如果您不再需要泛型,则没有必要编写泛型类:您可以将所有签名更改为 Any 并进行模式匹配。

  • 在大多数情况下,可以通过使用“typeclass”为集合实现类似 CanBuildFrom 方法的方法来避免递归类型

  • 最后,type-projection (FooModule#Foo) 的应用很少,您可能希望查看依赖于路径的类型。然而,这些也几乎没有应用。

于 2012-07-24T07:00:28.727 回答