Une façon qui a été suggérée pour traiter les doubles définitions des méthodes surchargées est de remplacer la surcharge par la correspondance de modèles:
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 abandonnions la vérification de type statique sur les arguments à foo
. Ce 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 Either
, mais ça devient vite 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 ressemble à une solution générale (élégante, efficace) , il faudrait définir Either3
, Either4
.... Est-ce que quelqu'un sait d'une solution de rechange pour atteindre le même but? A ma connaissance, Scala n'a pas de "disjonction de type" intégrée. De plus, les conversions implicites définies ci-dessus se cachent-elles quelque part dans la bibliothèque standard afin que je puisse simplement les importer?
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
.Either
approche semble être que nous perdons beaucoup de support du compilateur pour vérifier la correspondance.trait StringOrInt ...
StringOrInt[T]
àStringOrInt[-T]
(voir stackoverflow.com/questions/24387701/… )Miles Sabin décrit une très belle façon d'obtenir le type d'union dans son récent article de blog Les types d'union Unboxed dans Scala via l'isomorphisme Curry-Howard :
Il définit d'abord la négation des types comme
en utilisant la loi de De Morgan, cela lui permet de définir les types d'union
Avec les constructions auxiliaires suivantes
vous pouvez écrire des types d'union comme suit:
la source
Dotty , un nouveau compilateur Scala expérimental, prend en charge les types d'union (écrits
A | B
), vous pouvez donc faire exactement ce que vous vouliez:la source
Voici la manière Rex Kerr d'encoder les types d'union. Hétéro et simple!
Source: Commentaire n ° 27 sous cet excellent article de blog de Miles Sabin qui fournit une autre façon d'encoder les types d'union dans Scala.
la source
scala> f(9.2: AnyVal)
passe le vérificateur de type.trait Contra[-A] {}
à la place de toutes les fonctions à rien. Donc, vous obtenez des trucs commetype Union[A,B] = { type Check[Z] = Contra[Contra[Z]] <:< Contra[Contra[A] with Contra[B]] }
utilisés commedef f[T: Union[Int, String]#Check](t: T) = t match { case i: Int => i; case s: String => s.length }
(sans unicode sophistiqué).Il est possible de généraliser la solution de Daniel comme suit:
Les principaux inconvénients de cette approche sont
Either
approche, la généralisation plus , il faudrait définir analogueOr3
,Or4
etc. traits. Bien entendu, définir de tels traits serait beaucoup plus simple que définir lesEither
classes correspondantes .Mettre à jour:
Mitch Blevins démontre une approche très similaire et montre comment la généraliser à plus de deux types, en la surnommant le «bégaiement ou».
la source
Je suis en quelque sorte tombé sur une implémentation relativement propre des types d'union n-aire en combinant la notion de listes de types avec une simplification du travail de Miles Sabin dans ce domaine , que quelqu'un mentionne dans une autre réponse.
Étant donné le type
¬[-A]
qui est contravariant surA
, par définition étant donné queA <: B
nous pouvons écrire¬[B] <: ¬[A]
, inverser l'ordre des types.Étant donné les types
A
,B
etX
, nous voulons exprimerX <: A || X <: B
. En appliquant la contravariance, nous obtenons¬[A] <: ¬[X] || ¬[B] <: ¬[X]
. Cela peut à son tour être exprimé comme¬[A] with ¬[B] <: ¬[X]
dans lequel l'un deA
ouB
doit être un supertype deX
ouX
lui - même (pensez aux arguments de fonction).J'ai passé un certain temps à essayer de combiner cette idée avec une limite supérieure sur les types de membres comme on le voit dans le
TList
s de harrah / up , mais la mise en œuvre deMap
with type bounds s'est jusqu'à présent avérée difficile.la source
Function1
comme type contravariant existant. Vous n'avez pas besoin d'une implémentation, tout ce dont vous avez besoin est une preuve de conformité (<:<
).Une solution de classe de type est probablement la meilleure façon de procéder ici, en utilisant des implicits. Ceci est similaire à l'approche monoïde mentionnée dans le livre Odersky / Spoon / Venners:
Si vous exécutez ensuite ceci dans le REPL:
la source
Either
type préexistant de Scala tend à renforcer cette croyance. Utiliser des classes de types via les implicits de Scala est une meilleure solution au problème sous-jacent, mais c'est un concept relativement nouveau et encore peu connu, c'est pourquoi l'OP ne savait même pas les considérer comme une alternative possible à un type d'union.Nous aimerions un opérateur de type
Or[U,V]
qui puisse être utilisé pour contraindre un paramètre de typeX
de telle sorte que soitX <: U
ouX <: V
. Voici une définition qui se rapproche le plus possible:Voici comment il est utilisé:
Cela utilise quelques astuces de type Scala. Le principal est l'utilisation de contraintes de type généralisées . Types donnés
U
etV
, le compilateur Scala fournit une classe appeléeU <:< V
(et un objet implicite de cette classe) si et seulement si le compilateur Scala peut prouver qu'ilU
s'agit d'un sous-type deV
. Voici un exemple plus simple utilisant des contraintes de type généralisées qui fonctionne dans certains cas:Cet exemple fonctionne lorsqu'une
X
instance de classeB
, aString
ou a un type qui n'est ni un supertype ni un sous-type deB
ouString
. Dans les deux premiers cas, c'est vrai par la définition duwith
mot-clé que(B with String) <: B
et(B with String) <: String
, donc Scala fournira un objet implicite qui sera passé en tant queev
: le compilateur Scala acceptera correctementfoo[B]
etfoo[String]
.Dans le dernier cas, je me fie au fait que si
U with V <: X
, alorsU <: X
ouV <: X
. Cela semble intuitivement vrai, et je le suppose simplement. Il ressort clairement de cette hypothèse pourquoi cet exemple simple échoue quandX
est un supertype ou un sous-type de l'unB
ou l' autre ouString
: par exemple, dans l'exemple ci-dessus,foo[A]
est incorrectement accepté etfoo[C]
est incorrectement rejeté. Encore une fois, ce que nous voulons est une sorte de type expression sur les variablesU
,V
etX
qui est vrai exactement quandX <: U
ouX <: V
.La notion de contravariance de Scala peut aider ici. Vous vous souvenez du trait
trait Inv[-X]
? Parce qu'il est contravariant dans son paramètre de typeX
,Inv[X] <: Inv[Y]
si et seulement siY <: X
. Cela signifie que nous pouvons remplacer l'exemple ci-dessus par un exemple qui fonctionnera réellement:C'est parce que l'expression
(Inv[U] with Inv[V]) <: Inv[X]
est vraie, par la même hypothèse ci-dessus, exactement quandInv[U] <: Inv[X]
ouInv[V] <: Inv[X]
, et par la définition de contravariance, cela est vrai exactement quandX <: U
ouX <: 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:Scala tentera maintenant de construire le type
BOrString[X]
pour tout ceX
quifoo
est appelé avec, et le type sera construit précisément quandX
est un sous-type de l'unB
ou l' autreString
. Cela fonctionne, et il existe une notation abrégée. La syntaxe ci-dessous est équivalente (sauf qu'elleev
doit maintenant être référencée dans le corps de la méthode commeimplicitly[BOrString[X]]
plutôt que simplementev
) et utiliseBOrString
comme type lié au contexte :Ce que nous aimerions vraiment, c'est une manière 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 des fonctions sur des types tout comme nous curry des fonctions sur des valeurs. En d'autres termes, nous aimerions quelque chose comme ce qui suit:
Ce n'est pas directement possible dans Scala, mais il existe une astuce que nous pouvons utiliser pour nous en approcher. Cela nous amène à la définition de
Or
ci-dessus:Ici, nous utilisons le typage structurel et l' opérateur dièse de Scala pour créer un type structurel
Or[U,T]
qui est garanti d'avoir un type interne. C'est une étrange bête. Pour donner un peu de contexte, la fonctiondef bar[X <: { type Y = Int }](x : X) = {}
doit être appelée avec des sous-classes deAnyRef
qui ont un typeY
défini en eux: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 infixe pour l'opérateur de typeOr
, nous arrivons à notre définition originale defoo
: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
:la source
A|B <: A|B|C
problème? stackoverflow.com/questions/45255270/ ... Je ne peux pas dire.Il y a aussi ce hack :
Voir Contournement des ambiguïtés d'effacement de type (Scala) .
la source
(implicit e: DummyImplicit)
à l'une des signatures de type.Vous pourriez jeter un oeil à MetaScala , qui a quelque chose appelé
OneOf
. J'ai l'impression que cela ne fonctionne pas bien avec lesmatch
instructions mais que vous pouvez simuler la correspondance en utilisant des fonctions d'ordre supérieur. Jetez un œil à cet extrait de code , par exemple, mais notez que la partie "correspondance simulée" est commentée, peut-être parce qu'elle ne fonctionne pas encore tout à fait.Passons maintenant à un éditorial: je ne pense pas qu'il y ait quoi que ce soit d'extraordinaire à définir Either3, Either4, etc. comme vous le décrivez. Il s'agit essentiellement du double des 22 types de tuple standard intégrés à Scala. Ce serait certainement bien si Scala avait des types disjonctifs intégrés, et peut-être une syntaxe intéressante pour eux comme
{x, y, z}
.la source
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é.
Un problème est que Scala n'emploiera pas dans le contexte de correspondance de cas, une conversion implicite de
IntOfIntOrString
versInt
(etStringOfIntOrString
versString
), donc doit définir des extracteurs et utiliser à lacase Int(i)
place decase 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:
size(Left(2))
ousize(Right("test"))
.V
au lieu deOr
, par exempleIntVString
, `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 .
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, aD[Int ∨ String]
est attribuable à (c'est-à-dire est un sous-type de) aD[Int ∨ String ∨ Double]
.Apparemment, le compilateur Scala a trois bogues.
D[¬[Double]]
cas du match.3.
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 testercase _
dans laget
méthode, donc je n'aurais pas à déballer unOption
in thematch
insize()
.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.
La proposition de ma mise à jour précédente (pour un type d'union proche de première classe) a cassé le sous-typage.
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 êtreA
en position contravariante, de sorte que les supertypes deA
ne sont pas des sous - types deD[¬[A]]
niD[¬[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∨
.Le correctif complet est donc.
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 deA
.Nous pouvons confirmer les travaux de sous-typage.
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 unmatch
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.la source
size
fonction .size
n'accepte plusD[Any]
comme entrée.Il existe une autre manière qui est un peu plus facile à comprendre si vous ne grokez pas Curry-Howard:
J'utilise une technique similaire à Dijon
la source
Eh bien, tout cela est très intelligent, mais je suis presque sûr que vous savez déjà que les réponses à vos principales questions sont différentes sortes de «non». Scala gère la surcharge différemment et, il faut bien l'admettre, un peu moins élégamment que ce que vous décrivez. Une partie est due à l'interopérabilité Java, une partie est due au fait de ne pas vouloir frapper les cas délicats de l'algorithme d'inférence de type, et une partie est due au fait qu'il ne s'agit simplement pas de Haskell.
la source
Ajout aux réponses déjà excellentes ici. Voici un résumé qui s'appuie sur les types d'union de Miles Sabin (et les idées de Josh) mais les définit également de manière récursive, vous pouvez donc avoir> 2 types dans l'union (
def foo[A : UNil Or Int Or String Or List[String]
)https://gist.github.com/aishfenton/2bb3bfa12e0321acfc904a71dda9bfbb
NB: Je dois ajouter qu'après avoir joué avec ce qui précède pour un projet, j'ai fini par revenir à des types de somme simples (ie trait scellé avec des sous-classes). Les types d'union Miles Sabin sont parfaits pour restreindre le paramètre de type, mais si vous devez renvoyer un type d'union, cela n'offre pas grand-chose.
la source
A|C <: A|B|C
problème de sous - typage? stackoverflow.com/questions/45255270/... Mon instinct se sent NON parce que cela signifierait que celaA or C
devrait être le sous-type de(A or B) or C
mais qui ne contient pas le type,A or C
donc il n'y a aucun espoir de créerA or C
un sous-type deA or B or C
avec ce codage au moins .. . Qu'est-ce que tu penses ?À partir de la documentation , avec l'ajout de
sealed
:Concernant la
sealed
pièce:la source