Je pense que le type disjoint de première classe est un supertype scellé, avec les sous-types alternatifs, et des conversions implicites vers / depuis les types souhaités de disjonction vers ces sous-types alternatifs.
Je suppose que cela répond aux commentaires 33 - 36 de la solution de Miles Sabin, donc le premier type de classe qui peut être utilisé sur le site d'utilisation, mais je ne l'ai pas testé.
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 problème est que Scala n'emploiera pas dans le contexte de correspondance de cas, une conversion implicite de IntOfIntOrString
vers Int
(et StringOfIntOrString
vers String
), donc doit définir des extracteurs et utiliser à la case Int(i)
place de case i : Int
.
AJOUTER: J'ai répondu à Miles Sabin sur son blog comme suit. Peut-être y a-t-il plusieurs améliorations sur Soit:
- Il s'étend à plus de 2 types, sans aucun bruit supplémentaire sur le site d'utilisation ou de définition.
- Les arguments sont encadrés implicitement, par exemple n'ont pas besoin
size(Left(2))
ou size(Right("test"))
.
- La syntaxe de la correspondance de modèle est implicitement non boxée.
- Le boxing et le déballage peuvent être optimisés par le hotspot JVM.
- La syntaxe pourrait être celle adoptée par un futur type d'union de première classe, de sorte que la migration pourrait peut-être être transparente? Peut-être que pour le nom du type d'union, il serait préférable d'utiliser
V
au lieu de Or
, par exemple IntVString
, ` Int |v| String
`, ` Int or String
` ou mon favori ` Int|String
`?
MISE À JOUR: La négation logique de la disjonction pour le modèle ci-dessus suit, et j'ai ajouté un modèle alternatif (et probablement plus utile) sur le blog de 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)
UNE AUTRE MISE À JOUR: Concernant les commentaires 23 et 35 de la solution de Mile Sabin , voici un moyen de déclarer un type d'union sur le site d'utilisation. Notez qu'il est déballé après le premier niveau, c'est à dire qu'il a l'avantage d'être extensible à n'importe quel nombre de types dans la disjonction , alors que les Either
besoins de boxe imbriquée et le paradigme dans mon commentaire précédent 41 n'était pas extensible. En d'autres termes, a D[Int ∨ String]
est attribuable à (c'est-à-dire est un sous-type de) 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]])
^
Apparemment, le compilateur Scala a trois bogues.
- Il ne choisira pas la fonction implicite correcte pour aucun type après le premier type dans la disjonction de destination.
- Cela n'exclut pas le
D[¬[Double]]
cas du match.
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 {
^
La méthode get n'est pas contrainte correctement sur le type d'entrée, car le compilateur n'autorisera pas A
la position covariante. On pourrait dire que c'est un bogue car tout ce que nous voulons, ce sont des preuves, nous n'accédons jamais aux preuves dans la fonction. Et j'ai fait le choix de ne pas tester case _
dans la get
méthode, donc je n'aurais pas à déballer un Option
in the match
in size()
.
5 mars 2012: la mise à jour précédente doit être améliorée. La solution de Miles Sabin fonctionnait correctement avec le sous-typage.
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 proposition de ma mise à jour précédente (pour un type d'union proche de première classe) a cassé le sous-typage.
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)]]
^
Le problème est que A
dans (() => A) => A
apparaît à la fois dans les positions covariante (type de retour) et contravariante (entrée de fonction, ou dans ce cas une valeur de retour de fonction qui est une entrée de fonction), donc les substitutions ne peuvent être invariantes.
Notez que cela A => Nothing
n'est nécessaire que parce que nous voulons être A
en position contravariante, de sorte que les supertypes de A
ne sont pas des sous - types de D[¬[A]]
ni D[¬[A] with ¬[U]]
( voir aussi ). Comme nous n'avons besoin que d'une double contravariance, nous pouvons obtenir l'équivalent de la solution de Miles même si nous pouvons rejeter le ¬
et ∨
.
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]]]
^
Le correctif complet est donc.
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]
}
Notez que les 2 bogues précédents dans Scala restent, mais le troisième est évité car il T
est maintenant contraint d'être le sous-type de A
.
Nous pouvons confirmer les travaux de sous-typage.
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
J'ai pensé que les types d'intersection de première classe sont très importants, à la fois pour les raisons pour lesquelles Ceylan les a , et parce qu'au lieu de subsumer à Any
ce qui signifie que le déballage avec un match
type attendu peut générer une erreur d'exécution, le déballage d'une ( collection hétérogène contenant a) la disjonction peut être vérifiée (Scala doit corriger les bogues que j'ai notés). Les unions sont plus simples que la complexité de l'utilisation de la HList expérimentale de metascala pour des collections hétérogènes.
class StringOrInt[T]
est faitsealed
, la "fuite" à laquelle vous faites référence ("Bien sûr, cela pourrait être contourné par le code client en créant unStringOrInt[Boolean]
") est bouchée, du moins si elleStringOrInt
réside dans un fichier qui lui est propre. Ensuite, les objets témoins doivent être définis dans la même source queStringOrInt
.