我正在尝试创建类似于Scala库中的类型的Tuple,只使用:+方法通过添加N + 1st值将元组扩展为元组 - 这样我就能够以递归方式构造元组:
class Test {
abstract class Tuple {
//protected type Next[_] <: Tuple
//def :+[T](p: T): Next[T]
}
case class Tuple0() extends Tuple {
protected type Next[T] = Tuple1[T]
def :+[T](p: T): Next[T] = Tuple1(p)
}
case class Tuple1[+T1](p1: T1) extends Tuple {
protected type Next[T] = Tuple2[T1, T]
def :+[T](p: T): Next[T] = Tuple2(p1, p)
}
case class Tuple2[+T1, +T2](p1: T1, p2: T2) extends Tuple {
protected type Next[-T] = Nothing
def :+[T](p: T): Next[T] = throw new IndexOutOfBoundsException();
}
}
这个编译,但是一旦我取消注释Tuple的定义#Next,我得到:
Test.scala:13: error: covariant type T1 occurs in invariant position in type [T]Test.this.Tuple2[T1,T] of type Next
protected type Next[T] = Tuple2[T1, T]
^
one error found
为什么?你能提供一种解决方法,允许我递归地构建元组(混合的,类型安全的值类型)吗?
感谢。
答案 0 :(得分:5)
你可以做Mark Harrah在up中所做的事情:
sealed trait HList
case class HCons[+H, +T <: HList](head: H, tail: T) extends HList
{
def :+:[T](v : T) = HCons(v, this)
}
case object HNil extends HList
{
def :+:[T](v : T) = HCons(v, this)
}
也就是说,没有下一个类型的类型成员。可能有些事你不能做 这......你会注意到up's HList is not covariant for this reason。
如果有人能指出制作类型的一般方法,我真的很喜欢它 成员协变。我担心他们之所以不在我的头上,尽管可能 与Martin Oderksy's paper中的这句话有关:
超值会员 总是表现得很协调;类型成员变为不变量 它就是混凝土。这与斯卡利娜的事实有关 不允许对类型成员进行后期绑定。
虽然如果有人可以向我解释这句话,我会很高兴;)
编辑:这是另一种更接近您最初要求的方法。上 写它我意识到我不确定这是否真的会做你想要的... 也许你可以举例说明你打算如何使用这些元组?
由于我们不能拥有协变类型成员,我们可以使用“下一元组”逻辑 进入一个单独的特征:
trait Add {
type N[T]
type Add2[T] <: Add
def add[T](x: T): N[T]
def nextAdd[T](n: N[T]): Add2[T]
}
然后隐式转换为它:
class Tuple0Add extends Add {
type N[T1] = T1
type Add2[T1] = Tuple1Add[T1]
def add[T1](x: T1) = x
def nextAdd[T1](n: T1) = new Tuple1Add(n)
}
implicit def tuple0Add(t0: Unit) = new Tuple0Add
class Tuple1Add[T1](t1: T1) extends Add {
type N[T2] = (T1, T2)
type Add2[T2] = Nothing
def add[T2](x: T2) = (t1, x)
def nextAdd[T2](n: (T1,T2)) = sys.error("Can't go this far")
}
implicit def tuple1Add[T1](t1: T1) = new Tuple1Add(t1)
这是我发现有用的一般技术:Scala不会抱怨你 隐式地将协变类型转换为不变类型。
这使你可以做两件事,就像你使用常规元组做的那样:
1)逐步手动构建元组,并保留类型信息:
> val a = () add 1 add 2
> a._1
1
> a._2
2
2)动态构建元组,遗憾的是丢失类型信息:
def addAll(a: Add, s: List[_]): Any = s match {
case Nil => a
case x::Nil => a add x
case x::xs => addAll(a.nextAdd(a add x), xs)
}
> addAll((), List(1, 2))
(1, 2)
我们真正喜欢做什么 会写的
trait Add {
type N[T] <% Add
def add[T](x: T): N[T]
}
也就是说,确保在添加1个元素后,结果可以有更多
添加的东西;否则我们无法动态构建元组。
不幸的是,Scala不接受类型成员的视图边界。幸运的是,一个
view bound只不过是一个进行转换的方法;所以我们所有人
要做的是手动指定方法;因此nextAdd
。
这可能不是你想要的,但它可能会给你一些想法 如何更接近你的实际目标。
答案 1 :(得分:5)
shapeless中的HList
是完全协变的,支持转换为相应的元组类型。
你所遇到的问题(协变型变量出现在逆变位置)通常是通过“拉开差异”来避免的:基本的HList ADT元素是最低限度定义的,类似于Owen在他的答案顶部所做的方式,并且通过隐式转换添加了需要反复使用类型变量的定义。
tupling操作由正交机制提供:使用隐式类型定义(实际上是functional dependency)和依赖方法类型的组合在类型级别计算得到的元组类型(因此使用{{ 1}}或Scala 2.10-SNAPSHOT),
-Ydependent-method-types