簡體   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