Saya berpikir bahwa tipe disjoint kelas pertama adalah supertipe tertutup, dengan subtipe alternatif, dan konversi implisit ke / dari tipe disjunction yang diinginkan ke subtipe alternatif ini.
Saya menganggap ini membahas komentar 33 - 36 dari solusi Miles Sabin, jadi tipe kelas pertama yang dapat digunakan di situs penggunaan, tetapi saya tidak mengujinya.
sealed trait IntOrString
case class IntOfIntOrString( v:Int ) extends IntOrString
case class StringOfIntOrString( v:String ) extends IntOrString
implicit def IntToIntOfIntOrString( v:Int ) = new IntOfIntOrString(v)
implicit def StringToStringOfIntOrString( v:String ) = new StringOfIntOrString(v)
object Int {
def unapply( t : IntOrString ) : Option[Int] = t match {
case v : IntOfIntOrString => Some( v.v )
case _ => None
}
}
object String {
def unapply( t : IntOrString ) : Option[String] = t match {
case v : StringOfIntOrString => Some( v.v )
case _ => None
}
}
def size( t : IntOrString ) = t match {
case Int(i) => i
case String(s) => s.length
}
scala> size("test")
res0: Int = 4
scala> size(2)
res1: Int = 2
Satu masalah adalah Scala tidak akan menggunakan dalam konteks kasus pencocokan, konversi implisit dari IntOfIntOrString
ke Int
(dan StringOfIntOrString
ke String
), jadi harus mendefinisikan extractors dan menggunakan case Int(i)
alih-alih case i : Int
.
TAMBAH: Saya merespons Miles Sabin di blognya sebagai berikut. Mungkin ada beberapa perbaikan di antaranya:
- Itu meluas ke lebih dari 2 jenis, tanpa kebisingan tambahan di situs penggunaan atau definisi.
- Argumen dikotakkan secara implisit, misalnya tidak perlu
size(Left(2))
atau size(Right("test"))
.
- Sintaks dari pencocokan pola secara implisit unboxed.
- Boxing dan unboxing dapat dioptimalkan jauh oleh hotspot JVM.
- Sintaksnya bisa menjadi yang diadopsi oleh tipe serikat kelas satu di masa depan, jadi migrasi mungkin bisa mulus? Mungkin untuk nama jenis gabungan, akan lebih baik digunakan
V
daripada Or
, misalnya IntVString
, ` Int |v| String
`, ` Int or String
`, atau favorit saya ` Int|String
`?
UPDATE: Negasi logis dari disjungsi untuk pola di atas mengikuti, dan saya menambahkan alternatif (dan mungkin lebih bermanfaat) pola di blog Miles Sabin .
sealed trait `Int or String`
sealed trait `not an Int or String`
sealed trait `Int|String`[T,E]
case class `IntOf(Int|String)`( v:Int ) extends `Int|String`[Int,`Int or String`]
case class `StringOf(Int|String)`( v:String ) extends `Int|String`[String,`Int or String`]
case class `NotAn(Int|String)`[T]( v:T ) extends `Int|String`[T,`not an Int or String`]
implicit def `IntTo(IntOf(Int|String))`( v:Int ) = new `IntOf(Int|String)`(v)
implicit def `StringTo(StringOf(Int|String))`( v:String ) = new `StringOf(Int|String)`(v)
implicit def `AnyTo(NotAn(Int|String))`[T]( v:T ) = new `NotAn(Int|String)`[T](v)
def disjunction[T,E](x: `Int|String`[T,E])(implicit ev: E =:= `Int or String`) = x
def negationOfDisjunction[T,E](x: `Int|String`[T,E])(implicit ev: E =:= `not an Int or String`) = x
scala> disjunction(5)
res0: Int|String[Int,Int or String] = IntOf(Int|String)(5)
scala> disjunction("")
res1: Int|String[String,Int or String] = StringOf(Int|String)()
scala> disjunction(5.0)
error: could not find implicit value for parameter ev: =:=[not an Int or String,Int or String]
disjunction(5.0)
^
scala> negationOfDisjunction(5)
error: could not find implicit value for parameter ev: =:=[Int or String,not an Int or String]
negationOfDisjunction(5)
^
scala> negationOfDisjunction("")
error: could not find implicit value for parameter ev: =:=[Int or String,not an Int or String]
negationOfDisjunction("")
^
scala> negationOfDisjunction(5.0)
res5: Int|String[Double,not an Int or String] = NotAn(Int|String)(5.0)
PEMBARUAN LAIN: Mengenai komentar 23 dan 35 dari solusi Mile Sabin , berikut adalah cara untuk mendeklarasikan jenis persatuan di situs penggunaan. Catat itu tidak dikotak setelah tingkat pertama, yaitu memiliki keuntungan yang dapat diperpanjang untuk sejumlah jenis dalam disjungsi , sedangkan Either
kebutuhan bertinju tinju dan paradigma dalam komentar saya sebelumnya 41 tidak dapat diperpanjang. Dengan kata lain, a D[Int ∨ String]
ditugaskan untuk (yaitu subtipe dari) a D[Int ∨ String ∨ Double]
.
type ¬[A] = (() => A) => A
type ∨[T, U] = ¬[T] with ¬[U]
class D[-A](v: A) {
def get[T](f: (() => T)) = v match {
case x : ¬[T] => x(f)
}
}
def size(t: D[Int ∨ String]) = t match {
case x: D[¬[Int]] => x.get( () => 0 )
case x: D[¬[String]] => x.get( () => "" )
case x: D[¬[Double]] => x.get( () => 0.0 )
}
implicit def neg[A](x: A) = new D[¬[A]]( (f: (() => A)) => x )
scala> size(5)
res0: Any = 5
scala> size("")
error: type mismatch;
found : java.lang.String("")
required: D[?[Int,String]]
size("")
^
scala> size("hi" : D[¬[String]])
res2: Any = hi
scala> size(5.0 : D[¬[Double]])
error: type mismatch;
found : D[(() => Double) => Double]
required: D[?[Int,String]]
size(5.0 : D[?[Double]])
^
Rupanya kompiler Scala memiliki tiga bug.
- Ini tidak akan memilih fungsi implisit yang benar untuk semua jenis setelah jenis pertama dalam disjungsi tujuan.
- Itu tidak mengecualikan
D[¬[Double]]
kasing dari pertandingan.
3.
scala> class D[-A](v: A) {
def get[T](f: (() => T))(implicit e: A <:< ¬[T]) = v match {
case x : ¬[T] => x(f)
}
}
error: contravariant type A occurs in covariant position in
type <:<[A,(() => T) => T] of value e
def get[T](f: (() => T))(implicit e: A <:< ?[T]) = v match {
^
Metode get tidak dibatasi dengan benar pada tipe input, karena kompiler tidak akan memungkinkan A
dalam posisi kovarian. Orang mungkin berpendapat bahwa itu adalah bug karena yang kita inginkan hanyalah bukti, kita tidak pernah mengakses bukti dalam fungsinya. Dan saya membuat pilihan untuk tidak menguji case _
dalam get
metode ini, jadi saya tidak perlu membuka Option
kotak match
di dalam size()
.
05 Maret 2012: Pembaruan sebelumnya perlu ditingkatkan. Solusi Miles Sabin bekerja dengan benar dengan subtyping.
type ¬[A] = A => Nothing
type ∨[T, U] = ¬[T] with ¬[U]
class Super
class Sub extends Super
scala> implicitly[(Super ∨ String) <:< ¬[Super]]
res0: <:<[?[Super,String],(Super) => Nothing] =
scala> implicitly[(Super ∨ String) <:< ¬[Sub]]
res2: <:<[?[Super,String],(Sub) => Nothing] =
scala> implicitly[(Super ∨ String) <:< ¬[Any]]
error: could not find implicit value for parameter
e: <:<[?[Super,String],(Any) => Nothing]
implicitly[(Super ? String) <:< ?[Any]]
^
Proposal pembaruan saya sebelumnya (untuk tipe persatuan kelas dekat) mematahkan subtipe.
scala> implicitly[D[¬[Sub]] <:< D[(Super ∨ String)]]
error: could not find implicit value for parameter
e: <:<[D[(() => Sub) => Sub],D[?[Super,String]]]
implicitly[D[?[Sub]] <:< D[(Super ? String)]]
^
Masalahnya adalah bahwa A
dalam (() => A) => A
muncul di kedua kovarian (tipe kembali) dan contravarian (input fungsi, atau dalam hal ini nilai balik fungsi yang merupakan input fungsi) posisi, sehingga substitusi hanya dapat invarian.
Perhatikan bahwa A => Nothing
diperlukan hanya karena kita ingin A
di posisi contravariant, sehingga supertypes dari A
tidak subtipe dari D[¬[A]]
atau D[¬[A] with ¬[U]]
( lihat juga ). Karena kita hanya memerlukan kontravarian ganda, kita dapat mencapai yang setara dengan solusi Miles bahkan jika kita dapat membuang ¬
dan ∨
.
trait D[-A]
scala> implicitly[D[D[Super]] <:< D[D[Super] with D[String]]]
res0: <:<[D[D[Super]],D[D[Super] with D[String]]] =
scala> implicitly[D[D[Sub]] <:< D[D[Super] with D[String]]]
res1: <:<[D[D[Sub]],D[D[Super] with D[String]]] =
scala> implicitly[D[D[Any]] <:< D[D[Super] with D[String]]]
error: could not find implicit value for parameter
e: <:<[D[D[Any]],D[D[Super] with D[String]]]
implicitly[D[D[Any]] <:< D[D[Super] with D[String]]]
^
Jadi perbaikan lengkapnya adalah.
class D[-A] (v: A) {
def get[T <: A] = v match {
case x: T => x
}
}
implicit def neg[A](x: A) = new D[D[A]]( new D[A](x) )
def size(t: D[D[Int] with D[String]]) = t match {
case x: D[D[Int]] => x.get[D[Int]].get[Int]
case x: D[D[String]] => x.get[D[String]].get[String]
case x: D[D[Double]] => x.get[D[Double]].get[Double]
}
Catat 2 bug sebelumnya di Scala tetap, tetapi yang ke 3 dihindari karena T
sekarang dibatasi menjadi subtipe A
.
Kami dapat mengkonfirmasi karya subtyping.
def size(t: D[D[Super] with D[String]]) = t match {
case x: D[D[Super]] => x.get[D[Super]].get[Super]
case x: D[D[String]] => x.get[D[String]].get[String]
}
scala> size( new Super )
res7: Any = Super@1272e52
scala> size( new Sub )
res8: Any = Sub@1d941d7
Saya telah berpikir bahwa jenis persimpangan kelas sangat penting, baik untuk alasan Ceylon memiliki mereka , dan karena bukannya subsuming untuk Any
yang berarti pembukaan kemasan dengan match
pada jenis diharapkan dapat menghasilkan runtime error, yang unboxing dari ( koleksi heterogen yang mengandung a) disjungsi dapat diketikkan jenisnya (Scala harus memperbaiki bug yang saya catat). Serikat pekerja yang lebih mudah daripada kompleksitas menggunakan eksperimental HList dari metascala untuk koleksi heterogen.
class StringOrInt[T]
dibuatsealed
, "kebocoran" yang Anda maksudkan ("Tentu saja, ini bisa digerakkan oleh kode klien dengan membuatStringOrInt[Boolean]
") dicolokkan, setidaknya jikaStringOrInt
berada di file sendiri. Maka objek saksi harus didefinisikan dalam sumber yang sama denganStringOrInt
.