类型级编程中的协方差

jsa*_*ata 9 generics types scala

我正在尝试创建类似于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();
  }
}
Run Code Online (Sandbox Code Playgroud)

这个编译,但是一旦我取消注释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
Run Code Online (Sandbox Code Playgroud)

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

谢谢.

Owe*_*wen 5

你可以做什么马克哈拉做在:

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)
}
Run Code Online (Sandbox Code Playgroud)

也就是说,没有下一种类型的类型成员.可能有些事你不能这样做......你会注意到由于这个原因,up的HList不是协变的.

如果有人能指出一种使类型成员协变的一般方法,我真的很喜欢它.我担心他们之所以不在我的头上,尽管这可能与Martin Oderksy的论文中的这句话有关:

价值成员总是表现得很协调; 一个类型成员一旦具体化就变得不变.这与Scalina不承认类型成员的后期绑定有关.

虽然如果有人可以向我解释这句话我会很高兴;)


编辑:这是另一种更接近你原来要求的方法.在写这篇文章时,我意识到我不确定这是否会真正做你想要的...也许你可以举例说明你打算如何使用这些元组?

由于我们不能拥有协变类型成员,我们可以将"下一元组"逻辑放入一个单独的特征:

trait Add {
    type N[T]
    type Add2[T] <: Add

    def add[T](x: T): N[T]
    def nextAdd[T](n: N[T]): Add2[T]
}
Run Code Online (Sandbox Code Playgroud)

然后隐式转换为它:

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)
Run Code Online (Sandbox Code Playgroud)

这是我发现有用的一般技术:如果你隐式地将协变类型转换为不变类型,Scala不会抱怨.

然后,这使您可以执行两项高于常规元组的操作:

1)逐步手动构建元组,并保留类型信息:

> val a = () add 1 add 2
> a._1
1
> a._2
2
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

我们真正希望做的就是写作

trait Add {
    type N[T] <% Add

    def add[T](x: T): N[T]
}
Run Code Online (Sandbox Code Playgroud)

也就是说,确保在添加1个元素之后,结果可以添加更多内容; 否则我们无法动态构建元组.不幸的是,Scala不接受类型成员的视图边界.幸运的是,视图绑定只不过是一种进行转换的方法; 所以我们要做的就是手动指定方法; 因此nextAdd.

这可能不是你想要的,但它可能会给你一些如何更接近你的实际目标的想法.


Mil*_*bin 5

HList不成形完全协变,并支持转化为相应的元组类型.

你所遇到的问题(协变型变量出现在逆变位置)通常是通过"拉开方差"来避免的:基本的HList ADT元素是最低限度定义的,类似于Owen在他的答案顶部所做的方式,以及需要使用类型变量的定义通过隐式转换添加.

tupling操作由正交机制提供:使用隐式类型定义(实际上是函数依赖)和依赖方法类型的组合在类型级别计算得到的元组类型(因此使用-Ydependent-method-types或Scala 2.10-SNAPSHOT),

// Minimal base HList ADT elements
sealed trait HList

final case class HCons[+H, +T <: HList](head : H, tail : T) extends HList {
  def ::[U](h : U) : U :: H :: T = HCons(h, this)
}

trait HNil extends HList {
  def ::[H](h : H) = HCons(h, this)
}

case object HNil extends HNil

type ::[+H, +T <: HList] = HCons[H, T]

// Separate 'Ops` trait allows the HList type L to be used independently
// of variance.
final class HListOps[L <: HList](l : L) {
  // More definitions elided ...

  def tupled(implicit tupler : Tupler[L]) : tupler.Out = tupler(l)
}

// Implicitly pimp away the variance
implicit def hlistOps[L <: HList](l : L) = new HListOps(l)

// Type class representing a type-level function from the HList type to
// the corresponding tuple type
trait Tupler[L <: HList] {
  type Out <: Product
  def apply(l : L) : Out
}

// Type class instances for Tupler   
object Tupler {
  implicit def hlistTupler1[A] = new Tupler[A :: HNil] {
    type Out = Tuple1[A]
    def apply(l : A :: HNil) = Tuple1(l.head)
  }

  implicit def hlistTupler2[A, B] = new Tupler[A :: B :: HNil] {
    type Out = (A, B)
    def apply(l : A :: B :: HNil) = (l.head, l.tail.head)
  }

  // Add more instances for higher arities here ...
}

val t1 = (1 :: HNil).tupled           // type inferred as Tuple1[Int]
val t2 = (1 :: "foo" :: HNil).tupled  // type inferred as (Int, String)
Run Code Online (Sandbox Code Playgroud)