这是一个简单的例子:
{ // dependent type in function
def dep[B](a: Any, bs: Seq[B]): Seq[(a.type, B)] = {
val result: Seq[(a.type, B)] = bs.map { b =>
(a: a.type) -> (b: B)
}
result
}
val ss = dep(3, Seq("a"))
val ss2: Seq[(3, String)] = ss
}
它之所以有效,是因为
a.type
在调用站点自动解析为 3.type
,尽管 a
在定义站点甚至没有确定性路径。
到目前为止工作正常,但稍有改动,调用站点扩展将不再起作用:
{ // in case class
class Dep[B](a: Any, bs: Seq[B]) {
def result: Seq[(a.type, Any)] = {
val result: Seq[(a.type, B)] = bs.map { b =>
(a: a.type) -> (b: B)
}
result
}
}
object ss extends Dep(3, Seq("a"))
val ss2: Seq[(3, String)] = ss.result
}
/*
Found: Seq[((ss.a : Any), Any)]
Required: Seq[((3 : Int), String)]
Explanation
===========
Tree: ss.result
I tried to show that
Seq[((ss.a : Any), Any)]
conforms to
Seq[((3 : Int), String)]
but the comparison trace ended with `false`:
*/
由于
Dep
现在是类构造函数,调用站点将坚持其成员类型定义而不是调用站点类型。这造成了很多混乱并违反了构造函数原则,有没有办法增强编译器来统一这两种情况?
我能想到的最接近的是使用辅助构造函数,但它只会产生一些难以理解的编译错误:
{ // in case class, as auxiliary constructor
case class Dep[A, B](a: A, bs: Seq[B]) {
def this[B](a: Any, bs: Seq[B]) = this[a.type, B](a, bs)
def result: Seq[(A, Any)] = {
val result: Seq[(A, B)] = bs.map { b =>
(a: A) -> (b: B)
}
result
}
}
}
/*
None of the overloaded alternatives of constructor Dep in class Dep with types
[A, B](): Dep[A, B]
[A, B](a: A, bs: Seq[B]): Dep[A, B]
match arguments (Null)
*/
我认为构造函数不能有自己的类型参数(或构造不同类型的实例)。另一个问题是,即使您可以定义该构造函数,也会使其变得不明确(
new Dep(1, Seq("foo"))
与两者都匹配)。
出于同样的(后一个)原因,
apply
也不起作用。我能想到的最好的办法就是制作另一个“工厂方法”:
case class Dep[A, B](a: A, bs: Seq[B]) {
def result = bs.map { a -> _ }
}
object Dep {
def narrow[B](a: Any, bs: Seq[B]): Dep[a.type, B] = new Dep(a, bs)
}
Dep.narrow(3, Seq("foo", "bar")).result
如果您愿意交换参数
case class Dep[A, B](bs: Seq[B], a: A)
或添加额外的虚拟参数来解决歧义,那么您可以使用 apply
:
case class Dep[A, B](a: A, bs: Seq[B], dummy: Int) {
def result = bs.map { a -> _ }
}
object Dep {
def apply[B](a: Any, bs: Seq[B]): Dep[a.type, B] = new Dep(a, bs, 0)
}
Dep(3, Seq("foo", "bar")).result