6

Scalaで繰り返されるパラメータの型に存在する型のスコープを持つことは可能ですか?

動機

この回答では、次のケース クラスを使用します。

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

それは私が望むことをしますが、N(すべての行で同じであることを知る必要があることを除いて)気にしませんRect2D

私が試したこと

次のバージョンでは、間違ったセマンティクスが得られます。

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

存在は の下にある*ため、すべての行が同じ 2 番目の型パラメーターを持つという保証は得られません。

Rect2D(Sized(1, 2, 3), Sized(1, 2))

次のバージョンには、私が望むセマンティクスがあります。

case class Rect2D[A](rows: Seq[Sized[Seq[A], N]] forSome { type N <: Nat })

ここではforSome、existential を outer の上に持ち上げるために使用していSeqます。それは動作しますが、.in を書く必要はありませSeqRect2D(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 })

1 つ目は (当然のことながら) バージョンと同一_であり、2 つ目はコンパイルされません。

簡単な例

次の点を考慮してください。

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

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] の 2 番目のジェネリック パラメーターを使用しないため、キャプチャすることを気にしないということです。つまり、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))

存在パラメーターを使用してすべての行が同じ 2 番目の型であることを確認できない理由は、_ が「型」ではなく「不明な型」を意味するためです。

Seq[Seq[_]]

たとえば、すべての要素が Seq[_] 型である Seq を意味しますが、 _ が不明であるため、各 seq が同じ型であることを確認する可能性はありません。

クラスがケース クラスである必要がない場合、エレガンスの点で最善の解決策は、2 つのジェネリック パラメーター 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

簡単な例の答え

(以下の最初の例に対する回答)

それらがすべて同じである限り、 X[_]inの正確な型パラメーターは気にしないようです。ユーザーがこれを尊重しないcase class Y(xs: X[_]*)を作成できないようにしたいだけです。Y

これを実現する 1 つの方法は、デフォルトの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"))

最初の例への答え

上記のように、コンストラクターをプライベートにし、繰り返される引数リストを 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))

1つの欠点は、次のようなものを書くことができないことです

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))
//             ^^^^^                              ^^^^^^^^
于 2014-06-07T16:14:03.353 に答える
-1

簡単な例を見てみましょう。Yに追加の型パラメーターを宣言できます。

ケースクラスY[V](xs:X [V] *)

この型パラメーターは推測可能である必要があるため、ユーザーの観点から追加で書き込む必要はありません。

于 2012-07-18T12:34:46.527 に答える