类型级编程中的协变性 [英] Covariance in type-level programming

查看:86
本文介绍了类型级编程中的协变性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图创建与Scala库中的元组相同的类型Tuple,仅使用:+方法通过添加第N + 1个值将Tuple扩展为Tuple,以便我能够递归地构造元组:

pre $ 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)
}

案例类Tuple2 [+ T1,+ T2](p1:T1,p2:T2)扩展Tuple {
保护键入Next [-T] = Nothing
def:+ [T](p:T):Next [T] =抛出新的IndexOutOfBoundsException();






这个编译,但只要我取消注释的定义

  Test.scala:13:error:协变类型T1出现在类型[ T] Test.this.Tuple2 [T1,T]类型Next 
保护类型Next [T] = Tuple2 [T1,T]
^
找到一个错误

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



谢谢。

解决方案

您可以在 up a>:

 密封特质HList 

案例类HCons [+ H,+ T < HList](head:H,tail:T)将HList
{
def:+:[T](v:T)= HCons(v,this)
}

case object HNil extends HList
{
def:+:[T](v:T)= HCons(v,this)
}

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



如果有人可以指出一种通用的方法来创建类型$ b,我真的很喜欢它$ b成员协变。我担心他们不在我的头上的原因,尽管它可能与
有关。
Martin Oderksy的论文
$ b


值成员
总是表现为共变;一个类型成员变得不变为
,因为它变得具体。这与Scalina
不承认对类型成员的后期绑定有关。


虽然如果有人能解释对我来说这句话我会很高兴;)




编辑:这是另一种方法,它更接近你最初的问题对于。在
上写下来,我意识到我不确定这是否真的会做你想要的...
也许你可以举一个你如何打算使用这些元组的例子吗?



因为我们不能有协变类型成员,所以我们可以将下一个元组逻辑
放入一个单独的特征中:

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

def add [T ](x:T):N [T]
def nextAdd [T](n:N [T]):Add2 [T]
}

然后隐式转换为它:

  class Tuple0Add扩展添加{
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
$ b $ 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(不能走这么远)
}
隐式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 =>添加x
案例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不接受类型成员的视图边界。幸运的是,
的视图范围只不过是一种转换的方法;所以我们所有的b $ b都必须手动指定方法;因此 nextAdd



这可能不是您要查找的内容,但它可能会给您一些建议
如何更接近您的实际目标。

I'm trying to create types Tuple equivalent to the ones in the Scala library, only with a :+ method that extends a Tuple into a Tuple by addition of the N+1st value -- so that I will be able to construct Tuples recursively:

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();
  }
}

This compiles, but as soon as I uncomment the definition of Tuple#Next, I get:

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

Why is that? Can you provide a workaround which would allow me to build Tuples (of mixed, type-safe value types) recursively?

Thanks.

解决方案

You could do what Mark Harrah does in 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)
}

That is, don't have a type member for the next type. There may be things you can't do like this... you'll notice that up's HList is not covariant for this reason.

I would really like it if someone could point out a general way to make type members covariant. I'm afraid the reason why they are not is above my head, though it might have something to do with this sentence from Martin Oderksy's paper:

Value members always behave covariantly; a type member becomes invariant as soon as it is made concrete. This is related to the fact that Scalina does not admit late-binding for type members.

Though if someone could explain that sentence to me I would be delighted ;)


Edit: Here is another approach that is closer to what you originally asked for. On writing it I realized I'm not sure if this will really do what you want... maybe you could give an example of how you're intending to use these tuples?

Since we can't have covariant type members, we can put the "next tuple" logic into a separate trait:

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

    def add[T](x: T): N[T]
    def nextAdd[T](n: N[T]): Add2[T]
}

And then implicitly convert to it:

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)

This is a general technique I have found useful: Scala doesn't complain if you implicitly convert a covariant type to an invariant type.

This then allows you to do 2 things above what you could do with regular tuples:

1) Build a tuple manually in steps, and preserve type information:

> val a = () add 1 add 2
> a._1
1
> a._2
2

2) Build a tuple dynamically and, sadly, lose type information:

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)

What we really would have liked to do would be to have written

trait Add {
    type N[T] <% Add

    def add[T](x: T): N[T]
}

That is, ensure that after adding 1 element, the result can then have more things added to it; otherwise we could not build tuples dynamically. Unfortunately, Scala does not accept view bounds on type members. Luckily, a view bound is nothing more than a method that does the conversion; so all we have to do is manually specify the method; hence nextAdd.

This may not be what you are looking for, but maybe it will give you some ideas how to get closer to your actual goal.

这篇关于类型级编程中的协变性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆