是否可以在Scala中具有重复参数类型的存在类型范围?
在this answer中,我使用以下案例类:
case class Rect2D[A, N <: Nat](rows: Sized[Seq[A], N]*)
它做我想要的,但我不关心N
(除了需要知道它对所有行都是一样的),并且不希望在Rect2D
'中有它s类型参数列表。
以下版本给出了错误的语义:
case class Rect2D[A](rows: Sized[Seq[A], _ <: Nat]*)
存在主体位于*
之下,所以我不能保证所有行都具有相同的第二类参数 - 例如,以下编译,但不应该:
Rect2D(Sized(1, 2, 3), Sized(1, 2))
以下版本具有我想要的语义:
case class Rect2D[A](rows: Seq[Sized[Seq[A], N]] forSome { type N <: Nat })
我在这里使用forSome
来解除外部Seq
上的存在主义。它有效,但我不想在Seq
中写Rect2D(Seq(Sized(1, 2, 3), Sized(3, 4, 5)))
。
我尝试使用*
做类似的事情:
case class Rect2D[A](rows: Sized[Seq[A], N] forSome { type N <: Nat }*)
和
case class Rect2D[A](rows: Sized[Seq[A], N]* forSome { type N <: Nat })
第一个(毫不奇怪)与_
版本完全相同,第二个版本不编译。
请考虑以下事项:
case class X[A](a: A)
case class Y(xs: X[_]*)
我不希望编译Y(X(1), X("1"))
。确实如此。我知道我可以写:
case class Y(xs: Seq[X[B]] forSome { type B })
或者:
case class Y[B](xs: X[B]*)
但我想使用重复参数,不想在Y
上参数化B
。
答案 0 :(得分:1)
如果这不违反你的合同,因为你不关心N,你可以利用协方差将存在类型扔掉,如下所示:
trait Nat
trait Sized[A,+B<:Nat]
object Sized {
def apply[A,B<:Nat](natSomething:B,items: A *) = new Sized[Seq[A],B] {}
}
class NatImpl extends Nat
case class Rect2D[A](rows:Sized[Seq[A],Nat] * )
val sizedExample = Sized(new NatImpl,1,2,3)
Rect2D(Sized(new NatImpl,1,2,3),Sized(new NatImpl,1,2,3),Sized(new NatImpl,1,2,3))
这里的想法是你不关心捕获大小[A,B]的第二个泛型参数,因为你没有使用它。所以你在B中创建了类协变,这意味着
如果Sized[A,B] <:< Sized[A,C]
B<:<C
存在类型的问题是你要求它对于传递给Rect2D的构造函数的所有对象都是相同的,但显然这是不可能的,因为它是一个存在类型,所以编译器无法验证它。 / p>
如果你不能使它协变但是有争议,那么同样的方法也会起作用:你在B中使这个类具有控制性:
如果Sized[A,B] <:< Sized[A,C]
,请 C<:<B
然后你可以利用事实Nothing是一切的子类:
trait Nat
trait Sized[A,-B<:Nat]
object Sized {
def apply[A,B<:Nat](natSomething:B,items: A *) = new Sized[Seq[A],B] {}
}
class NatImpl extends Nat
case class Rect2D[A](rows:Sized[Seq[A],Nothing] * )
val sizedExample = Sized(new NatImpl,1,2,3)
Rect2D(Sized(new NatImpl,1,2,3),Sized(new NatImpl,1,2,3),Sized(new NatImpl,1,2,3))
你不能使用存在性参数来验证所有行具有相同的第二种类型的原因是因为_不代表“一种类型”而是“一种未知类型”
序号[SEQ [_]]
例如表示Seq,其中每个元素都是Seq [_]类型,但由于_未知,因此不可能验证每个seq具有相同的类型。
如果你的课不必是一个案例类,那么优雅方面的最佳解决方案是使用方差/控制方法与私有构造函数,使用两个泛型参数,A和N
答案 1 :(得分:1)
注意:之前我有一个不同的,不起作用的解决方案,但是我把它编辑了。
编辑:现在版本4
sealed trait Rect2D[A] extends Product with Serializable { this: Inner[A] =>
val rows: Seq[Sized[Seq[A], N]] forSome { type N <: Nat }
def copy(rows: Seq[Sized[Seq[A], N]] forSome { type N <: Nat } = this.rows): Rect2D[A]
}
object Rect2D {
private[Rect2D] case class Inner[A](rows: Seq[Sized[Seq[A], N]] forSome { type N <: Nat }) extends Rect2D[A]
def apply[A, N <: Nat](rows: Sized[Seq[A], N]*): Rect2D[A] = Inner[A](rows)
def unapply[A](r2d: Rect2D[A]): Option[Seq[Sized[Seq[A], N]] forSome { type N <: Nat }] = Inner.unapply(r2d.asInstanceOf[Inner[A]])
}
最后,一个“与案例类一起工作”的版本!我敢肯定,如果我只知道如何使用它们,大部分内容都可以通过宏来消除。
答案 2 :(得分:0)
(以下第一个例子回答)
看起来您并不关心X[_]
中case class Y(xs: X[_]*)
的精确类型参数,只要它们完全相同即可。您只是想阻止用户创建不尊重这一点的Y
。
实现这一目标的一种方法是将默认的Y
构造函数设为私有:
case class Y private (xs: Seq[X[_]])
// ^^^^^^^ makes the default constructor private to Y, xs is still public
// Note also that xs is now a Seq, we will recover the repeated arg list below.
并以这种方式定义自己的构造函数:
object Y {
def apply[B](): Y = Y(Nil)
def apply[B](x0: X[B], xs: X[B]*): Y = Y(x0 +: xs)
// Note that this is equivalent to
// def apply[B](xs: X[B]*): Y = Y(xs)
// but the latter conflicts with the default (now private) constructor
}
现在可以写
Y()
Y(X("a"))
Y(X(1), X(1), X(5), X(6))
Y[Int](X(1), X(1), X(5), X(6))
以下不编译:
Y(X(1), X("1"))
我们将构造函数设为私有,并将重复的arg列表更改为Seq,如上所述:
case class Rect2D[A] private (rows: Seq[Sized[Seq[A], _]])
// ^^^^^^^ ^^^^ ^
让我们定义自己的构造函数:
object Rect2D {
def apply[A](): Rect2D[A] = Rect2D[A](Nil)
def apply[A,N <: Nat](r0: Sized[Seq[A], N], rs: Sized[Seq[A], N]*): Rect2D[A] = Rect2D[A](r0 +: rs)
}
现在进行以下编译:
val r0: Rect2D[_] = Rect2D()
val r: Rect2D[Int] = Rect2D[Int]()
val r1: Rect2D[Int] = Rect2D(Sized[Seq](1, 2))
val r2: Rect2D[Int] = Rect2D(Sized[Seq](1, 2), Sized[Seq](2, 3))
val r3: Rect2D[Int] = Rect2D(Sized[Seq](1, 2), Sized[Seq](2, 3), Sized[Seq](2, 3), Sized[Seq](2, 3))
val r4: Rect2D[Any] = Rect2D(Sized[Seq](1, 2), Sized[Seq]("a", "b"), Sized[Seq](2, 3), Sized[Seq](2, 3)) // Works because both Sized and Seq are covariant
// Types added as a check, they can be removed
以下没有:
val r5 = Rect2D(Sized[Seq](1, 2), Sized[Seq](1, 2, 3))
一个缺点是无法写出像
这样的东西val r2 = Rect2D[Int](Sized[Seq](1, 2), Sized[Seq](2, 3))
// ^^^^^
一个人必须写这个
val r2 = Rect2D[Int, Nat._2](Sized[Seq](1, 2), Sized[Seq](2, 3))
// ^^^^^^^^
让我们解决这个问题!
更清洁的解决方案是以这种方式定义构造函数:
object Rect2D {
def apply[A,N <: Nat](r0: Sized[Seq[A], N], rs: Sized[Seq[A], N]*): Rect2D[A] = Rect2D[A](r0 +: rs) // Same as above
case class Rect2DBuilder[A]() {
def apply(): Rect2D[A] = Rect2D[A](Nil)
def apply[N <: Nat](r0: Sized[Seq[A], N], rs: Sized[Seq[A], N]*): Rect2D[A] = Rect2D[A](r0 +: rs)
}
def apply[A] = new Rect2DBuilder[A]
}
现在我们也可以写
val r2 = Rect2D[Int](Sized[Seq](1, 2), Sized[Seq](2, 3))
以下不会编译
val r4 = Rect2D[Int](Sized[Seq](1, 2), Sized[Seq]("a", "b"), Sized[Seq](2, 3), Sized[Seq](2, 3))
// ^^^^^ ^^^^^^^^
答案 3 :(得分:-1)
以简化为例: 你可以在Y上声明一个额外的类型参数:
案例类Y [V](xs:X [V] *)
这个类型参数应该是可以推断的,所以从用户的角度来看,没有什么可写的。