Myślę, że rozłącznym typem pierwszej klasy jest zapieczętowany nadtyp, z alternatywnymi podtypami i niejawnymi konwersjami do / z pożądanych typów rozłączenia na te alternatywne podtypy.
Zakładam, że odnosi się to do komentarzy 33 - 36 rozwiązania Milesa Sabina, więc jest to typ pierwszej klasy, który można zastosować na stronie użytkowania, ale go nie przetestowałem.
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
Jednym z problemów jest to, że Scala nie zastosuje w przypadku dopasowania kontekstu, niejawnej konwersji z IntOfIntOrStringna Int(i StringOfIntOrStringna String), więc należy zdefiniować ekstraktory i używać case Int(i)zamiast case i : Int.
DODAJ: Odpowiedziałem Milesowi Sabinowi na jego blogu w następujący sposób. Być może istnieje kilka ulepszeń w stosunku do któregokolwiek:
- Rozciąga się na więcej niż 2 typy, bez dodatkowego hałasu w miejscu użytkowania lub definicji.
- Argumenty są umieszczane domyślnie, np. Nie trzeba
size(Left(2))lub size(Right("test")).
- Składnia dopasowania wzorca jest niejawnie rozpakowana.
- Boksowanie i rozpakowywanie może być zoptymalizowane przez hotspot JVM.
- Składnia może być przyjęta przez przyszły pierwszy typ unii, więc migracja może być płynna? Być może dla nazwy typu unii lepiej byłoby użyć
Vzamiast Ornp. IntVString` Int |v| String`, ` Int or String` Lub mojego ulubionego ` Int|String`?
AKTUALIZACJA: Następuje logiczna negacja niezgodności powyższego wzoru, a ja dodałem alternatywny (i prawdopodobnie bardziej użyteczny) wzór na blogu Milesa Sabina .
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)
KOLEJNA AKTUALIZACJA: Jeśli chodzi o komentarze 23 i 35 dotyczące rozwiązania Mile Sabin , oto sposób zadeklarowania typu związku na stronie użytkowania. Zauważ, że jest rozpakowywany po pierwszym poziomie, tj. Ma tę zaletę, że może być rozszerzany na dowolną liczbę typów w rozłączeniu , podczas gdy Eitherwymaga zagnieżdżenia boksu, a paradygmat w moim poprzednim komentarzu 41 nie był rozszerzalny. Innymi słowy, a D[Int ∨ String]można przypisać (tj. Jest podtypem) 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]])
^
Najwyraźniej kompilator Scala ma trzy błędy.
- Nie wybierze prawidłowej funkcji niejawnej dla dowolnego typu po pierwszym typie w rozłączeniu docelowym.
- Nie wyklucza to
D[¬[Double]]przypadku z meczu.
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 {
^
Metoda get nie jest poprawnie ograniczona do typu danych wejściowych, ponieważ kompilator nie pozwala Ana pozycję kowariantną. Można argumentować, że jest to błąd, ponieważ wszystko czego chcemy to dowód, nigdy nie uzyskujemy dostępu do dowodów w funkcji. I dokonał wyboru nie do badania case _w getsposób, więc nie będzie musiał unbox Optionw matchw size().
05 marca 2012: poprzednia aktualizacja wymaga ulepszenia. Rozwiązanie Milesa Sabina działało poprawnie z podtytułem.
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]]
^
Moja poprzednia propozycja aktualizacji (dla typu unii prawie pierwszej klasy) złamała podtytuł.
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)]]
^
Problem polega na tym, że Ain (() => A) => Apojawia się zarówno w pozycjach kowariantnej (typ zwracany), jak i kontrawariantnej (wejście funkcji, lub w tym przypadku wartość zwracana funkcji, która jest wejściem funkcji), dlatego podstawienia mogą być tylko niezmienne.
Należy pamiętać, że A => Nothingjest to konieczne tylko dlatego, że chcemy Aw pozycji kontrawariantny, tak że supertypes z A nie podtypów z D[¬[A]]ani D[¬[A] with ¬[U]]( patrz również ). Ponieważ potrzebujemy tylko podwójnej sprzeczności, możemy osiągnąć równowartość rozwiązania Milesa, nawet jeśli możemy odrzucić ¬i ∨.
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]]]
^
Tak więc pełna poprawka jest.
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]
}
Zwróć uwagę, że pozostały 2 poprzednie błędy w Scali, ale trzeciego unika się, ponieważ Tobecnie jest ograniczony do podtypu A.
Możemy potwierdzić prace podtypów.
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
Myślałem, że typy przecięcia pierwszej klasy są bardzo ważne, zarówno dla powodów Ceylon je ma , i dlatego zamiast podporządkowania do Anyjakich środków unboxing z matchoczekiwanych typów może wygenerować błąd wykonywania, unboxing o ( kolekcja heterogenicznym zawierającym a) rozłączność można sprawdzić pod kątem typu (Scala musi naprawić błędy, które zauważyłem). Związki są bardziej proste niż złożoności przy użyciu eksperymentalnego HList z metascala dla zbiorów heterogenicznych.
class StringOrInt[T]tak się staniesealed, „wyciek”, o którym mówiłeś („Oczywiście kod klienta może utworzyć krok po kroku, tworzącStringOrInt[Boolean]„), jest zatkany, przynajmniej jeśliStringOrIntznajduje się we własnym pliku. Następnie obiekty świadka muszą być zdefiniowane w tym samym sosie coStringOrInt.