【问题标题】:Avoiding redundant generic parameters in Scala避免 Scala 中的冗余泛型参数
【发布时间】:2016-11-09 15:59:01
【问题描述】:

所以这是 this Java question 到 scala 的一个相当直接的端口

我们有一堆采用通用参数的特征,如下所示:

 trait Ident { }

 trait Container[I <: Ident] {
   def foo(id: I): String
 }

 trait Entity[C <: Container[I], I <: Ident] {
   def container: C
   def foo(id: I) = container.foo(id)
 }

这可行,但有点笨拙,因为在定义实体的子类时,我们必须提供 Ident 的类型和 Container 的类型。实际上,仅 Container 的类型本身就足够了类型信息:

class MyIdent extends Ident { }
class MyContainer extends Container[MyIdent] { } 
class MyEntity extends Entity[MyContainer,MyIdent] { }
//                                        ^^^^^^^ shouldn't really be necessary

使用存在类型可以避免 Entity 需要两个参数...但是当然你以后不能引用它。

trait Entity[C <: Container[I] forSome { type I <: Ident }] {
  def container: C
  def foo(id: I) = container.foo(id)
//           ^^^ complains it has no idea what 'I' is here
}

同样将事物转换为使用成员类型也行不通...

trait Ident { }

trait Container {
  type I <: Ident
  def foo(id: I): String
}

trait Entity {
  type C <: Container
  def container: C
  def foo(id: C#I) = container.foo(id)
//                                 ^^ type mismatch
}

那么有谁知道 Scala 中是否有一个优雅的解决方案来解决这个问题?

【问题讨论】:

  • 我认为答案与 Java 版本不会有太大不同。没有办法在不丢失类型参数的情况下省略类型参数。
  • 是否可以选择将container 设为val
  • @MichaelZajac 因此,在“MyEntity”的定义中,提供给 Entity 的第二个参数是多余的:除了“MyIdent”之外,您根本没有其他可能的类型可以使用,实际上任何其他类型都会给出编译错误。当然,能否避免 Scala 中的冗余是另一个问题:-)
  • 检查this answer,了解我在回答中提出的类似内容如何导致不健全

标签: scala scala-generics


【解决方案1】:

更新 鉴于this answer我不确定这是否应该被视为一个错误

你打了SI-4377;如果你提供明确的type ascriptions,你会得到一个错误,我猜这只是暴露了类型投影是使用existentials实现的:

trait Ident { }

trait Container {
  type I <: Ident
  def foo(id: I): String
}

trait Entity {

  type C <: Container
  def container: C
  def foo(id: C#I): String = (container: C).foo(id: C#I)
  // you will get something like: type mismatch;
  // [error]  found   : Entity.this.C#I
  // [error]  required: _3.I where val _3: Entity.this.C
  // as I said above, see https://issues.scala-lang.org/browse/SI-4377
}

毫不夸张地说,这(错误?)使使用类型成员的泛型编程成为一场噩梦。

一个技巧,其中包括将 values 转换为手工制作的自引用类型别名:

case object Container {

  type is[C <: Container] = C with Container {

    type I = C#I
    // same for all other type members, if any
  }

  def is[C <: Container](c: C): is[C] = c.asInstanceOf[is[C]]
}

现在使用它,Entity 编译:

trait Entity {

  type C <: Container
  def container: C
  def foo(id: C#I): String = Container.is(container).foo(id)
  // compiles!
}

这当然很危险,根据经验,只有当C 及其所有类型成员在使用时绑定到非抽象类型时才是安全的;请注意,情况并非总是如此,因为 Scala 允许您保留“未定义”类型成员:

case object funnyContainer extends Container {

  // I'm forced to implement `foo`, but *not* the `C` type member
  def foo(id: I): String = "hi scalac!"
}

【讨论】:

  • 谢谢,这行得通。很高兴知道我的想法是对的,但只是遇到了一个错误:-)
猜你喜欢
  • 2011-06-16
  • 1970-01-01
  • 2015-12-09
  • 1970-01-01
  • 2020-09-10
  • 2019-01-02
  • 2011-01-31
  • 1970-01-01
  • 2017-08-10
相关资源
最近更新 更多