【问题标题】:Existential types and repeated parameters存在类型和重复参数
【发布时间】:2012-07-18 11:49:54
【问题描述】:

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

动机

this answer我使用以下案例类:

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

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

我尝试过的东西

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

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

existential 在* 下,所以我不能保证所有行都具有相同的第二个类型参数——例如,以下编译,但不应该:

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

我尝试用*做类似的事情:

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

【问题讨论】:

  • 在您的简化示例中,case class Y[B](xs: X[B]*) 不是一个有效的解决方案吗?
  • @Nicolas:对不起,这个问题没有明确表明我试图避免这种解决方案(这正是我在链接答案中使用的方法)。我进行了编辑以使其更清晰。

标签: scala types


【解决方案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))

这里的想法是您不关心捕获 Sized[A,B] 的第二个通用参数,因为您不使用它。所以你在 B 中使类协变,这意味着 Sized[A,B] &lt;:&lt; Sized[A,C] 如果B&lt;:&lt;C

存在类型的问题是您要求传递给 Rect2D 构造函数的所有对象都相同,但显然这是不可能的,因为它是存在类型,因此编译器无法验证它。

如果你不能使它成为协变的但可以变变,同样的方法也可以:你在 B 中使类变变:

Sized[A,B] &lt;:&lt; Sized[A,C] 如果C&lt;:&lt;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 具有相同的类型。

如果您的类不必是案例类,那么就优雅而言,最好的解决方案是使用带有私有构造函数的方差/逆变方法,具有两个泛型参数 A 和 N

【讨论】:

  • 谢谢,但我确实关心参数,因为我需要它对所有项目都相同。编译器可以case class Y(xs: Seq[X[B]] forSome { type B })的情况下验证这一点,所以我不明白为什么重复参数是不可能的。
  • 响应您的更新:我可以使用forSome 将存在的范围提升到外部Seq 之上。我想对重复的参数版本做同样的事情。
  • case class Y(xs: X[B]* forSome { type B }) 只是无法编译。
  • scala> class X[A] defined class X scala> case class Y(xs: X[B] forSome {type B } *) defined class Y
  • 很遗憾,这与_ 版本相同。
【解决方案2】:

注意:我之前在这里有一个不同的、无效的解决方案,但我将其编辑了。

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

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

【讨论】:

  • 谢谢——我没想过要试试这个,但遗憾的是存在主义仍在*:Rect2D(Sized(1, 2), Sized(3)) 下编译但不应该。
  • 第二个版本的几个问题:需要移动第一行中的括号并在 N 之前添加 type 以使语法正常工作。然后我仍然收到apply 的“擦除后相同类型”错误。但是如果我删除case 并只使构造函数private,它给了我我想要的用法。所以这比迄今为止的任何事情都更接近,但由于它不适用于案例类,我将在接受之前坚持一段时间。非常感谢——
【解决方案3】:

简单例子的答案

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

看起来您并不关心case class Y(xs: X[_]*)X[_] 的精确类型参数,只要它们都相同即可。你只是想阻止用户创建不尊重这一点的Ys。

实现此目的的一种方法是将默认的 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))
//             ^^^^^                              ^^^^^^^^

【讨论】:

    【解决方案4】:

    举个简单的例子: 你可以在 Y 上声明一个额外的类型参数:

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

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

    【讨论】:

      猜你喜欢
      • 2020-08-28
      • 2017-12-31
      • 1970-01-01
      • 1970-01-01
      • 2016-01-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多