在 Scala 中避免冗余泛型参数
Avoiding redundant generic parameters in Scala
所以这是 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)
}
这可行,但有点笨拙,因为我们在定义实体的子class 时必须提供Ident 的类型和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 中是否有解决这个问题的优雅方法吗?
更新 给定我不确定这是否应该被视为一个错误
你点击了 SI-4377; if you provide explicit type ascriptions 你会得到一个错误,我猜它只是暴露了类型投影是使用存在性实现的:
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
}
可以毫不夸张地说,这个(错误?)使类型成员的泛型编程成为一场噩梦。
虽然 是 一个 hack,它包括将 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 允许您保留 "undefined" 类型成员:
case object funnyContainer extends Container {
// I'm forced to implement `foo`, but *not* the `C` type member
def foo(id: I): String = "hi scalac!"
}
所以这是 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)
}
这可行,但有点笨拙,因为我们在定义实体的子class 时必须提供Ident 的类型和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 中是否有解决这个问题的优雅方法吗?
更新 给定
你点击了 SI-4377; if you provide explicit type ascriptions 你会得到一个错误,我猜它只是暴露了类型投影是使用存在性实现的:
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
}
可以毫不夸张地说,这个(错误?)使类型成员的泛型编程成为一场噩梦。
虽然 是 一个 hack,它包括将 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 允许您保留 "undefined" 类型成员:
case object funnyContainer extends Container {
// I'm forced to implement `foo`, but *not* the `C` type member
def foo(id: I): String = "hi scalac!"
}