Sto pensando che il tipo disgiunto di prima classe è un supertipo sigillato, con sottotipi alternativi e conversioni implicite da / verso i tipi desiderati di disgiunzione a questi sottotipi alternativi.
Suppongo che questo indirizzi i commenti 33 - 36 della soluzione di Miles Sabin, quindi il tipo di prima classe che può essere impiegato sul sito di utilizzo, ma non l'ho provato.
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
Un problema è Scala sarà non impiegare nel contesto caso di corrispondenza, una conversione implicita da IntOfIntOrString
a Int
(e StringOfIntOrString
per String
), quindi deve definire estrattori e utilizzare case Int(i)
invece case i : Int
.
AGGIUNGI: Ho risposto a Miles Sabin sul suo blog come segue. Forse ci sono diversi miglioramenti su entrambi:
- Si estende a più di 2 tipi, senza alcun rumore aggiuntivo nel sito di utilizzo o definizione.
- Gli argomenti sono inscatolati implicitamente, ad esempio non è necessario
size(Left(2))
osize(Right("test"))
.
- La sintassi della corrispondenza del modello è implicitamente unboxed.
- La boxe e unboxing possono essere ottimizzati dall'hotspot JVM.
- La sintassi potrebbe essere quella adottata da un futuro tipo di unione di prima classe, quindi la migrazione potrebbe forse essere senza soluzione di continuità? Forse per il nome del tipo di unione, sarebbe meglio usare
V
invece Or
, ad esempio IntVString
, ` Int |v| String
`, ` Int or String
` o il mio preferito ` Int|String
`?
AGGIORNAMENTO: Segue la logica negazione della disgiunzione per il modello sopra, e ho aggiunto un modello alternativo (e probabilmente più utile) sul blog di 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)
UN ALTRO AGGIORNAMENTO: per quanto riguarda i commenti 23 e 35 della soluzione di Mile Sabin , ecco un modo per dichiarare un tipo di unione nel sito di utilizzo. Si noti che è unboxed dopo il primo livello, cioè ha il vantaggio di essere estensibile a qualsiasi numero di tipi nella disgiunzione , mentre ha Either
bisogno di boxe nidificate e il paradigma nel mio precedente commento 41 non era estensibile. In altre parole, a D[Int ∨ String]
è assegnabile a (cioè è un sottotipo di) 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]])
^
Apparentemente il compilatore Scala ha tre bug.
- Non sceglierà la funzione implicita corretta per nessun tipo dopo il primo tipo nella disgiunzione di destinazione.
- Non esclude il
D[¬[Double]]
caso dalla partita.
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 {
^
Il metodo get non è vincolato correttamente al tipo di input, perché il compilatore non lo consentirà A
nella posizione covariante. Si potrebbe obiettare che si tratta di un bug perché tutto ciò che vogliamo è l'evidenza, non accediamo mai all'evidenza nella funzione. E ho fatto la scelta di non provare case _
nel get
metodo, quindi non avrei dovuto deselezionare un Option
in match
in size()
.
05 marzo 2012: l'aggiornamento precedente deve essere migliorato. La soluzione di Miles Sabin ha funzionato correttamente con il sottotipo.
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]]
^
La proposta del mio precedente aggiornamento (per un tipo di unione quasi di prima classe) ha rotto il sottotitolo.
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)]]
^
Il problema è che A
dentro(() => A) => A
appare sia nella posizione covariante (tipo di ritorno) sia in quella controversa (input di funzione, o in questo caso un valore di ritorno della funzione che è un input di funzione), quindi le sostituzioni possono essere solo invarianti.
Si noti che A => Nothing
è necessario solo perché vogliamo A
nella posizione controversa, in modo che i supertipi di A
non siano sottotipi di D[¬[A]]
né D[¬[A] with ¬[U]]
( vedi anche ). Dato che abbiamo solo bisogno di una doppia contraddizione, possiamo raggiungere l'equivalente della soluzione di Miles anche se possiamo scartare il ¬
e ∨
.
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]]]
^
Quindi la soluzione completa è.
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]
}
Notare che rimangono i 2 bug precedenti in Scala, ma il terzo viene evitato in quanto T
è ora vincolato al sottotipo A
.
Possiamo confermare i lavori di sottotipizzazione.
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
Ho pensato che i tipi intersezione di prima classe sono molto importanti, sia per i motivi Ceylon li ha , e perché invece di sussumere a Any
quali mezzi unboxing con una match
sui tipi che ci si attende in grado di generare un errore di runtime, l'unboxing di un ( collezione eterogenea contenente a) la disgiunzione può essere verificata (Scala deve correggere i bug che ho notato). I sindacati sono più semplici della complessità dell'uso della HList sperimentale di metascala per raccolte eterogenee.
class StringOrInt[T]
viene creatosealed
, la "perdita" a cui si fa riferimento ("Naturalmente, questo potrebbe essere evitato dal codice client creando unStringOrInt[Boolean]
") viene inserito, almeno seStringOrInt
risiede in un file a sé stante. Quindi gli oggetti testimone devono essere definiti nello stesso formato diStringOrInt
.