web-dev-qa-db-fra.com

Comment définir "type disjunction" (types d'union)?

Une façon dont a été suggéré de traiter les définitions doubles des méthodes surchargées consiste à remplacer la surcharge par une correspondance de modèle:

object Bar {
   def foo(xs: Any*) = xs foreach { 
      case _:String => println("str")
      case _:Int => println("int")
      case _ => throw new UglyRuntimeException()
   }
}

Cette approche nécessite que nous soumettions une vérification de type statique sur les arguments à foo. Il serait beaucoup plus agréable de pouvoir écrire

object Bar {
   def foo(xs: (String or Int)*) = xs foreach {
      case _: String => println("str")
      case _: Int => println("int")
   }
}

Je peux me rapprocher de Either, mais cela devient rapidement moche avec plus de deux types:

type or[L,R] = Either[L,R]

implicit def l2Or[L,R](l: L): L or R = Left(l)
implicit def r2Or[L,R](r: R): L or R = Right(r)

object Bar {
   def foo(xs: (String or Int)*) = xs foreach {
      case Left(l) => println("str")
      case Right(r) => println("int")
   }
}

Il semble qu'une solution générale (élégante, efficace) nécessiterait de définir Either3, Either4, .... Quelqu'un connaît-il une solution alternative pour atteindre le même objectif? À ma connaissance, Scala n'a pas de "disjonction de type" intégrée. De plus, les conversions implicites définies ci-dessus sont-elles cachées quelque part dans la bibliothèque standard afin que je puisse simplement les importer?

167
Aaron Novstrup

Eh bien, dans le cas spécifique de Any*, cette astuce ci-dessous ne fonctionnera pas, car elle n'acceptera pas les types mélangés. Cependant, étant donné que les types mixtes ne fonctionneraient pas non plus avec une surcharge, cela peut être ce que vous voulez.

Commencez par déclarer une classe avec les types que vous souhaitez accepter comme ci-dessous:

class StringOrInt[T]
object StringOrInt {
  implicit object IntWitness extends StringOrInt[Int]
  implicit object StringWitness extends StringOrInt[String]
}

Ensuite, déclarez foo comme ceci:

object Bar {
  def foo[T: StringOrInt](x: T) = x match {
    case _: String => println("str")
    case _: Int => println("int")
  }
}

Et c'est tout. Vous pouvez appeler foo(5) ou foo("abc"), et cela fonctionnera, mais essayez foo(true) et cela échouera. Cela pourrait être évité par le code client en créant un StringOrInt[Boolean], sauf si, comme indiqué par Randall ci-dessous, vous faites de StringOrInt une classe sealed.

Cela fonctionne parce que T: StringOrInt signifie qu'il existe un paramètre implicite de type StringOrInt[T] et que Scala examine l'intérieur des objets compagnon d'un type pour voir s'il existe des implications pour que le code qui demande ce type fonctionne.

132
Daniel C. Sobral

Miles Sabin décrit une manière très agréable d’obtenir le type d’union dans son récent article de blog Les types d’union non emballés à Scala via l’isomorphisme de Curry-Howard :

Il définit d'abord la négation des types comme

type ¬[A] = A => Nothing

en utilisant la loi de De Morgan cela lui permet de définir les types d'union

type ∨[T, U] = ¬[¬[T] with ¬[U]]

Avec les constructions auxiliaires suivantes 

type ¬¬[A] = ¬[¬[A]]
type |∨|[T, U] = { type λ[X] = ¬¬[X] <:< (T ∨ U) }

vous pouvez écrire des types d'union comme suit:

def size[T : (Int |∨| String)#λ](t : T) = t match {
    case i : Int => i
    case s : String => s.length
}
170
michid

Dotty , un nouveau compilateur expérimental Scala, supporte les types d'union (écrit A | B), vous permettant ainsi de faire exactement ce que vous vouliez:

def foo(xs: (String | Int)*) = xs foreach {
   case _: String => println("str")
   case _: Int => println("int")
}
38
Samuel Gruetter

Voici la méthode Rex Kerr pour coder les types d'union. Droit et simple!

scala> def f[A](a: A)(implicit ev: (Int with String) <:< A) = a match {
     |   case i: Int => i + 1
     |   case s: String => s.length
     | }
f: [A](a: A)(implicit ev: <:<[Int with String,A])Int

scala> f(3)
res0: Int = 4

scala> f("hello")
res1: Int = 5

scala> f(9.2)
<console>:9: error: Cannot prove that Int with String <:< Double.
       f(9.2)
        ^

Source: Commentaire n ° 27 sous this excellent billet de blog de Miles Sabin, qui fournit un autre moyen d’encoder les types d’union dans Scala.

29
missingfaktor

Il est possible de généraliser la solution de Daniel comme suit:

sealed trait Or[A, B]

object Or {
   implicit def a2Or[A,B](a: A) = new Or[A, B] {}
   implicit def b2Or[A,B](b: B) = new Or[A, B] {}
}

object Bar {
   def foo[T <% String Or Int](x: T) = x match {
     case _: String => println("str")
     case _: Int => println("int")
   }
}

Les principaux inconvénients de cette approche sont 

  • Comme Daniel l'a souligné, il ne gère pas les collections/varargs avec des types mélangés
  • Le compilateur n'émet aucun avertissement si la correspondance n'est pas exhaustive
  • Le compilateur n'émet pas d'erreur si la correspondance inclut un cas impossible
  • A l'instar de l'approche Either, une généralisation plus poussée nécessiterait de définir des traits analogues Or3, Or4, etc. Bien entendu, définir de tels traits serait beaucoup plus simple que de définir les classes Either correspondantes.

Mettre à jour:

Mitch Blevins démontreune approche très similaire et montre comment le généraliser à plus de deux types, le surnommant "bégaiement ou".

14
Aaron Novstrup

Je suis en quelque sorte tombé sur une mise en œuvre relativement propre des types d'union n-aire en combinant la notion de listes de types avec une simplification de le travail de Miles Sabin dans ce domaine , que quelqu'un mentionne dans une autre réponse.

Étant donné le type ¬[-A] qui est contravariant sur A, par définition donné A <: B, nous pouvons écrire ¬[B] <: ¬[A], en inversant le classement des types.

Étant donné les types A, B et X, nous souhaitons exprimer X <: A || X <: B. En appliquant une contravariance, nous obtenons ¬[A] <: ¬[X] || ¬[B] <: ¬[X]. Ceci peut à son tourbe être exprimé en ¬[A] with ¬[B] <: ¬[X] dans lequel l'un de A ou B doit être un sur-type de X ou X lui-même (pensez aux arguments de la fonction).

object Union {
  import scala.language.higherKinds

  sealed trait ¬[-A]

  sealed trait TSet {
    type Compound[A]
    type Map[F[_]] <: TSet
  }

  sealed trait ∅ extends TSet {
    type Compound[A] = A
    type Map[F[_]] = ∅ 
  }

  // Note that this type is left-associative for the sake of concision.
  sealed trait ∨[T <: TSet, H] extends TSet {
    // Given a type of the form `∅ ∨ A ∨ B ∨ ...` and parameter `X`, we want to produce the type
    // `¬[A] with ¬[B] with ... <:< ¬[X]`.
    type Member[X] = T#Map[¬]#Compound[¬[H]] <:< ¬[X]

    // This could be generalized as a fold, but for concision we leave it as is.
    type Compound[A] = T#Compound[H with A]

    type Map[F[_]] = T#Map[F] ∨ F[H]
  }

  def foo[A : (∅ ∨ String ∨ Int ∨ List[Int])#Member](a: A): String = a match {
    case s: String => "String"
    case i: Int => "Int"
    case l: List[_] => "List[Int]"
  }

  foo(42)
  foo("bar")
  foo(List(1, 2, 3))
  foo(42d) // error
  foo[Any](???) // error
}

J'ai passé un certain temps à essayer de combiner cette idée avec une limite supérieure sur les types de membre, comme indiqué dans les variables TLists de harrah/up , mais la mise en œuvre de Map avec des limites de type s'est jusqu'à présent révélée difficile.

13
J Cracknell

Une solution de type est probablement la solution la plus intéressante, utilisant implicits . Ceci est similaire à l'approche monoïde mentionnée dans le livre Odersky/Spoon/Venners:

abstract class NameOf[T] {
  def get : String
}

implicit object NameOfStr extends NameOf[String] {
  def get = "str"
}

implicit object NameOfInt extends NameOf[Int] {
 def get = "int"
}

def printNameOf[T](t:T)(implicit name : NameOf[T]) = println(name.get)

Si vous exécutez ensuite ceci dans le REPL:

scala> printNameOf(1)
int

scala> printNameOf("sss")
str

scala> printNameOf(2.0f)
<console>:10: error: could not find implicit value for parameter nameOf: NameOf[
Float]
       printNameOf(2.0f)

              ^
12
Kevin Wright

Nous préférerions un opérateur de type Or[U,V] pouvant être utilisé pour contraindre un paramètre de type X de manière à ce que X <: U ou X <: V. Voici une définition qui se rapproche le plus possible:

trait Inv[-X]
type Or[U,T] = {
    type pf[X] = (Inv[U] with Inv[T]) <:< Inv[X]
}

Voici comment il est utilisé:

// use

class A; class B extends A; class C extends B

def foo[X : (B Or String)#pf] = {}

foo[B]      // OK
foo[C]      // OK
foo[String] // OK
foo[A]      // ERROR!
foo[Number] // ERROR!

Cela utilise quelques astuces de type Scala. La principale est l'utilisation de contraintes de type generalized . Avec les types U et V, le compilateur Scala fournit une classe appelée U <:< V (et un objet implicite de cette classe) si et seulement si le compilateur Scala peut prouver que U est un sous-type de V. Voici un exemple plus simple utilisant des contraintes de type généralisées qui fonctionne dans certains cas:

def foo[X](implicit ev : (B with String) <:< X) = {}

Cet exemple fonctionne lorsque X est une instance de la classe B, une String, ou a un type qui n'est ni un supertype ni un sous-type de B ou String. Dans les deux premiers cas, la définition du mot clé with correspond à (B with String) <: B et (B with String) <: String. Scala fournira donc un objet implicite qui sera passé sous la forme ev: le compilateur Scala acceptera correctement foo[B] et foo[String]

Dans le dernier cas, je me fie au fait que si U with V <: X, alors U <: X ou V <: X. Cela semble intuitivement vrai, et je l’assume simplement. Cette hypothèse explique clairement pourquoi cet exemple simple échoue lorsque X est un supertype ou un sous-type de B ou String: par exemple, dans l'exemple ci-dessus, foo[A] est incorrectement accepté et foo[C] est incorrectement rejeté. Encore une fois, nous voulons une sorte d’expression de type sur les variables U, V et X qui est vraie exactement lorsque X <: U ou X <: V.

La notion de contravariance de Scala peut aider ici. Rappelez-vous le trait trait Inv[-X]? Parce qu'il est contravariant dans son paramètre de type X, Inv[X] <: Inv[Y] si et seulement si Y <: X. Cela signifie que nous pouvons remplacer l'exemple ci-dessus par un exemple qui fonctionnera réellement: 

trait Inv[-X]
def foo[X](implicit ev : (Inv[B] with Inv[String]) <:< Inv[X]) = {}

C’est parce que l’expression (Inv[U] with Inv[V]) <: Inv[X] est vraie, selon la même hypothèse ci-dessus, exactement quand Inv[U] <: Inv[X] ou Inv[V] <: Inv[X] et à la définition de contravariance, c’est vrai exactement quand X <: U ou X <: V.

Il est possible de rendre les choses un peu plus réutilisables en déclarant un type paramétrable BOrString[X] et en l’utilisant comme suit:

trait Inv[-X]
type BOrString[X] = (Inv[B] with Inv[String]) <:< Inv[X]
def foo[X](implicit ev : BOrString[X]) = {}

Scala va maintenant tenter de construire le type BOrString[X] pour chaque X avec laquelle foo est appelé, et le type sera construit précisément lorsque X est un sous-type de B ou String. Cela fonctionne et il y a une notation abrégée. La syntaxe ci-dessous est équivalente (sauf que ev doit maintenant être référencé dans le corps de la méthode sous la forme implicitly[BOrString[X]] plutôt que simplement ev) et utilise BOrString comme un contexte type lié :

def foo[X : BOrString] = {}

Ce que nous n'aimons pas vraiment, c’est un moyen flexible de créer un type lié au contexte. Un contexte de type doit être un type paramétrable, et nous voulons un moyen paramétrable pour en créer un. On dirait que nous essayons de curry les fonctions sur les types comme nous le faisons sur les valeurs. En d’autres termes, nous aimerions quelque chose comme ce qui suit:

type Or[U,T][X] = (Inv[U] with Inv[T]) <:< Inv[X]

C’est pas directement possible à Scala, mais il existe un truc que nous pouvons utiliser pour nous rapprocher un peu plus. Cela nous amène à la définition de Or ci-dessus:

trait Inv[-X]
type Or[U,T] = {
    type pf[X] = (Inv[U] with Inv[T]) <:< Inv[X]
}

Ici, nous utilisons structural typing et l’opérateur pound de Scala pour créer un type structurel Or[U,T] dont le type interne est garanti. Ceci est une bête étrange. Pour donner un certain contexte, la fonction def bar[X <: { type Y = Int }](x : X) = {} doit être appelée avec les sous-classes de AnyRef qui ont un type Y défini dans celles-ci:

bar(new AnyRef{ type Y = Int }) // works!

L'utilisation de l'opérateur dièse nous permet de faire référence au type interne Or[B, String]#pf, et en utilisant la notation infix pour l'opérateur de type Or, nous arrivons à notre définition originale de foo:

def foo[X : (B Or String)#pf] = {}

Nous pouvons utiliser le fait que les types de fonction sont contravariants dans leur premier paramètre de type afin d'éviter de définir le trait Inv:

type Or[U,T] = {
    type pf[X] = ((U => _) with (T => _)) <:< (X => _)
} 
9
Josh

Il y a aussi ce hack:

implicit val x: Int = 0
def foo(a: List[Int])(implicit ignore: Int) { }

implicit val y = ""
def foo(a: List[String])(implicit ignore: String) { }

foo(1::2::Nil)
foo("a"::"b"::Nil)

Voir Résolution des ambiguïtés d'effacement de type (Scala).

8
michid

Vous pouvez jeter un oeil à MetaScala , qui a quelque chose qui s'appelle OneOf . J'ai l'impression que cela ne fonctionne pas bien avec les instructions match mais que vous pouvez simuler la correspondance à l'aide de fonctions d'ordre supérieur. Jetez un coup d'œil à cet extrait , par exemple, mais notez que la partie "correspondance simulée" est commentée, peut-être parce que cela ne fonctionne pas encore.

Maintenant, pour quelques éditoriaux: je ne pense pas qu'il y ait quelque chose de grave à définir Either3, Either4, etc. comme vous le décrivez. Ceci est essentiellement le double des 22 types standard Tuple intégrés à Scala. Ce serait certainement bien si Scala avait des types disjonctifs intégrés, et peut-être une syntaxe de Nice pour eux comme {x, y, z}.

7
Tom Crockett

Je pense que le type disjoint de la première classe est un supertype scellé, avec les sous-types alternatifs et les conversions implicites vers/à partir des types souhaités de la disjonction en ces sous-types alternatifs.

Je suppose que cela adresse commentaires 33 - 36 de la solution de Miles Sabin, donc le premier type de classe pouvant ê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

L'un des problèmes est que Scala n'utilisera pas, dans le contexte de correspondance de casse, une conversion implicite de IntOfIntOrString à Int (et StringOfIntOrString à String), il faut donc définir des extracteurs et utiliser case Int(i) au lieu de case i : Int.


ADD: J'ai répondu à Miles Sabin sur son blog comme suit. Peut-être y a-t-il plusieurs améliorations par rapport à:

  1. Il s'étend à plus de 2 types, sans aucun bruit supplémentaire sur le site d'utilisation ou de définition.
  2. Les arguments sont mis en boîte de manière implicite, par ex. pas besoin de size(Left(2)) ou size(Right("test")).
  3. La syntaxe de la correspondance de modèle est implicitement sans boîte.
  4. La boxe et le déballage peuvent être optimisés à distance par le hotspot JVM.
  5. 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 de 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 préféré `Int|String`?

UPDATE: Suit la négation logique de la disjonction pour le motif ci-dessus, et I ajouté un motif 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)

AUTRE MISE À JOUR: En ce qui concerne 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’elle est déballée après le premier niveau, c’est-à-dire qu’elle a l’avantage d’être extensible à un nombre quelconque de types dans la disjonction , alors que Either nécessite une boxe imbriquée et que le paradigme de mon commentaire précédent 41 n’était pas extensible. En d’autres termes, un D[Int ∨ String] est assignable à (c’est un sous-type de) un 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.

  1. Il ne choisira pas la fonction implicite correcte pour aucun type après le premier type dans la disjonction de destination.
  2. Cela n'exclut pas la casse D[¬[Double]] de la correspondance.

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 correctement contrainte sur le type d'entrée, car le compilateur n'autorisera pas A dans la position covariante. On pourrait dire que c'est un bogue parce que tout ce que nous voulons, c'est des preuves, nous n'avons jamais accès aux preuves dans la fonction. Et j’ai fait le choix de ne pas tester case _ dans la méthode get, de sorte que je n’aurais pas à déballer une Option dans match dans size().


05 mars 2012: La mise à jour précédente doit être améliorée. _ { 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 précédente mise à jour (pour un type d'union proche de la première classe) rompait 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 covariant (type de retour) et contravariant (entrée de fonction, ou dans ce cas une valeur de retour de fonction qui est une entrée de fonction); les substitutions ne peuvent donc être qu'invariantes.

Notez que A => Nothing est nécessaire uniquement parce que nous voulons A dans la position contravariante, de sorte que les supertypes de Ane sont pas des sous-types de D[¬[A]] et D[¬[A] with ¬[U]] ( voir aussi ). Etant donné que nous n’avons besoin que d’une double contravariance, nous pouvons obtenir l’équivalent de la solution de Miles même si nous pouvons supprimer le ¬ et le .

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]]]
                 ^

Donc, le correctif complet est.

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 demeurent, mais le 3ème est évité car T est maintenant contraint d’être un 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

Je pensais 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 subsuming à Any, ce qui signifie unboxing avec un match sur les types attendus peut générer une erreur d’exécution, le désemballage d’une ( collection hétérogène } contenant une disjonction) peut être vérifié au type (Scala doit corriger les bogues que j’ai notés). Les syndicats sont plus simples que la complexité d'utilisation l'expérimentale HList de metascala pour les collections hétérogènes.

6
Shelby Moore III

Il y a une autre façon un peu plus facile à comprendre si vous ne parlez pas Curry-Howard:

type v[A,B] = Either[Option[A], Option[B]]

private def L[A,B](a: A): v[A,B] = Left(Some(a))
private def R[A,B](b: B): v[A,B] = Right(Some(b))  
// TODO: for more use scala macro to generate this for up to 22 types?
implicit def a2[A,B](a: A): v[A,B] = L(a)
implicit def b2[A,B](b: B): v[A,B] = R(b)
implicit def a3[A,B,C](a: A): v[v[A,B],C] = L(a2(a))
implicit def b3[A,B,C](b: B): v[v[A,B],C] = L(b2(b))
implicit def a4[A,B,C,D](a: A): v[v[v[A,B],C],D] = L(a3(a))
implicit def b4[A,B,C,D](b: B): v[v[v[A,B],C],D] = L(b3(b))    
implicit def a5[A,B,C,D,E](a: A): v[v[v[v[A,B],C],D],E] = L(a4(a))
implicit def b5[A,B,C,D,E](b: B): v[v[v[v[A,B],C],D],E] = L(b4(b))

type JsonPrimtives = (String v Int v Double)
type ValidJsonPrimitive[A] = A => JsonPrimtives

def test[A : ValidJsonPrimitive](x: A): A = x 

test("hi")
test(9)
// test(true)   // does not compile

J'utilise une technique similaire en dijon

4
pathikrit

Ajoutant aux réponses déjà excellentes ici. Voici un résumé qui s'appuie sur les types d'union Miles Sabin (et sur les idées de Josh), mais les définit également de manière récursive, de sorte que vous pouvez avoir plus de 2 types dans l'union (def foo[A : UNil Or Int Or String Or List[String]).

https://Gist.github.com/aishfenton/2bb3bfa12e0321acfc904a71dda9bfbb

NB: Je devrais ajouter qu'après avoir manipulé ce qui précède pour un projet, j'ai fini par revenir aux types de somme simple (c'est-à-dire un trait scellé avec des sous-classes). Les types d'union Miles Sabin sont parfaits pour restreindre le paramètre type, mais si vous devez renvoyer un type d'union, il n'offre pas grand chose.

1
Aish

Eh bien, tout cela est très intelligent, mais je suis sûr que vous savez déjà que les réponses à vos questions principales sont diverses variétés de "Non". Scala gère la surcharge différemment et, il faut bien l'avouer, un peu moins élégant que vous ne le décrivez. Cela est dû en partie à l'interopérabilité de Java, au fait de ne pas vouloir toucher aux cas délicats de l'algorithme d'inférence de type, et en partie au fait que ce n'est tout simplement pas Haskell. 

1
Dave Griffith

De les documents , avec l'ajout de sealed:

sealed class Expr
case class Var   (x: String)          extends Expr
case class Apply (f: Expr, e: Expr)   extends Expr
case class Lambda(x: String, e: Expr) extends Expr

En ce qui concerne la partie sealed:

Il est possible de définir d'autres classes de cas qui étendent le type Expr dans d'autres parties du programme (...). Cette forme d'extensibilité peut être exclue en déclarant la classe de base Expr scellée; dans ce cas, toutes les classes qui étendent directement Expr doivent se trouver dans le même fichier source qu'Expr.

0
Elazar