繁体   English   中英

存在类型和重复参数

[英]Existential types and repeated parameters

是否可以在Scala中具有重复参数类型的存在类型范围?

动机

这个答案中,我使用以下案例类:

case class Rect2D[A, N <: Nat](rows: Sized[Seq[A], N]*)

它做我想要的,但我不关心N (除了需要知道它对所有行都是一样的),并且不希望在Rect2D的类型参数列表中有它。

我试过的东西

以下版本给出了错误的语义:

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的存在主义。 它有效,但我不想在Rect2D(Seq(Sized(1, 2, 3), Sized(3, 4, 5)))编写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]*)

但我想使用重复的参数,不想在B上参数化Y

如果这不违反你的合同,因为你不关心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的构造函数的所有对象都是相同的,但显然这是不可能的,因为它是一个存在类型,所以编译器无法验证它。

如果你不能使它协变但是有争议,那么同样的方法也会起作用:你在B中使这个类具有控制性:

如果C<:<B Sized[A,B] <:< Sized[A,C]

然后你可以利用事实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

注意:我之前有一个不同的,非工作的解决方案,但我编辑了它。

编辑:现在版本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]])
}

最后,一个“与案例类一起工作”的版本! 我敢肯定,如果我只知道如何使用它们,大部分内容都可以通过宏来消除。

回答简化的例子

(以下第一个例子的答案)

case class Y(xs: X[_]*) ,看起来你并不关心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))
//             ^^^^^                              ^^^^^^^^

以简化为例:您可以在Y上声明一个额外的类型参数:

案例类Y [V](xs:X [V] *)

这个类型参数应该是可以推断的,所以从用户的角度来看,没有什么可写的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM