Usted podría hacer lo que hace la marca Harrah en 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)
}
Es decir, no tienen un miembro de tipo para el siguiente tipo. Puede haber cosas que no puede hacer como esto ... notará que up's HList is not covariant for this reason.
Me encantaría que alguien pudiera señalar una forma general de hacer que los miembros del tipo sean covariantes. Me temo que la razón por la que no se está por encima de la cabeza, aunque podría tener algo que ver con esta frase de Martin Oderksy's paper:
miembros de valor siempre se comportan covariantly; un miembro de tipo se vuelve invariante como tan pronto como se concrete. Esto está relacionado con el hecho de que Scalina no admite la vinculación tardía para miembros de tipo.
Aunque si alguien podría explicar esa frase me estaría encantado;)
Editar: Aquí hay otro enfoque que está más cerca de lo que usted pidió originalmente. En escribiéndolo, me di cuenta de que no estoy seguro de si esto realmente hará lo que usted desea ... ¿Podría dar un ejemplo de cómo piensa utilizar estas tuplas?
Puesto que no podemos tener miembros de tipo covariantes, podemos poner el "siguiente tupla" lógica en un rasgo separado:
trait Add {
type N[T]
type Add2[T] <: Add
def add[T](x: T): N[T]
def nextAdd[T](n: N[T]): Add2[T]
}
Y entonces implícitamente convertir a la misma:
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)
Esta es una técnica general que he encontrado útil: Scala no se queja si convierte implícitamente un tipo de covariante a un tipo invariante.
Este continuación, le permite hacer 2 cosas por encima de lo que podría hacer con tuplas regulares:
1) Construir una tupla manualmente en pasos, y preservar la información del tipo:
> val a =() add 1 add 2
> a._1
1
> a._2
2
2) Construir una tupla de forma dinámica y, por desgracia, perder la información del tipo:
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)
Lo que realmente hubiera gustado hacer sería tener por escrito
trait Add {
type N[T] <% Add
def add[T](x: T): N[T]
}
Es decir, garantizar que después de la adición de 1 elemento, el resultado puede entonces tener más cosas añadidas a la misma; de lo contrario, no podríamos construir tuplas dinámicamente. Desafortunadamente, Scala no acepta límites de vista en los miembros de tipo. Afortunadamente, un límite de vista de no es más que un método que realiza la conversión; entonces todo lo que tenemos que hacer es especificar manualmente el método; por lo tanto, nextAdd
.
Esto puede no ser lo que está buscando, pero tal vez le dará algunas ideas cómo acercarse a su objetivo real.
Pondré los tipos de datos adjuntos en una jerarquía de tipos separada que se resuelve con una resolución implícita. De esa forma puedes usar las clases normales de Scala tuple. –