6

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。它有效,但我不想写Seqin 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]*)

但我想使用重复的参数,不想YB.

4

4 回答 4

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] <:< Sized[A,C]如果B<:<C

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

如果你不能使它成为协变的,但它是协变的,同样的方法将起作用:你在 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 具有相同的类型。

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

于 2012-07-18T13:17:48.500 回答
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]])
}

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

于 2012-07-18T22:36:34.190 回答
0

Answer for the simplified example

(answer for the first example below)

It looks like you don't care about the precise type parameter of the X[_] in case class Y(xs: X[_]*), as long as they are all the same. You just want to prevent users to create Ys that don't respect this.

One way to achive this would be to make the default Y constructor private:

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.

and define your own constructor this way:

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
}

Now one can write

Y()
Y(X("a"))
Y(X(1), X(1), X(5), X(6))
Y[Int](X(1), X(1), X(5), X(6))

and the following doesn't compile:

Y(X(1), X("1"))

Answer to the first example

We make the constructor private and change the repeated arg list to a Seq as above:

case class Rect2D[A] private (rows: Seq[Sized[Seq[A], _]])
//                   ^^^^^^^        ^^^^                ^

Let's define our own constructor(s):

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

Now the following compiles:

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

and the following doesn't:

val r5 = Rect2D(Sized[Seq](1, 2), Sized[Seq](1, 2, 3))

One drawback is that one cannot write something like

val r2 = Rect2D[Int](Sized[Seq](1, 2), Sized[Seq](2, 3))
//             ^^^^^

one has to write this instead

val r2 = Rect2D[Int, Nat._2](Sized[Seq](1, 2), Sized[Seq](2, 3))
//                 ^^^^^^^^

Let's fix this!

Enhanced solution for the first example

A cleaner solution would be define the constructors above this way:

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]

}

Now we can also write

val r2 = Rect2D[Int](Sized[Seq](1, 2), Sized[Seq](2, 3))

and the following would not compile

val r4 = Rect2D[Int](Sized[Seq](1, 2), Sized[Seq]("a", "b"), Sized[Seq](2, 3), Sized[Seq](2, 3))
//             ^^^^^                              ^^^^^^^^
于 2014-06-07T16:14:03.353 回答
-1

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

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

此类型参数应该是可推断的,因此从用户的角度来看,无需编写任何额外内容。

于 2012-07-18T12:34:46.527 回答