类型级编程中的协方差

时间:2011-11-09 14:43:40

标签: generics scala types

我正在尝试创建类似于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

为什么?你能提供一种解决方法,允许我递归地构建元组(混合的,类型安全的值类型)吗?

感谢。

2 个答案:

答案 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