cas partagés en F # syndicats discriminés
-
02-10-2019 - |
Question
Je veux écrire quelque chose comme ceci:
type NumExp = Num of float
type Exp =
| Num of float
| Dot of NumExp * NumExp
| Op of string * Exp * Exp
let getValue (Num(n) : NumExp) = n
Le compilateur se plaint d'un conflit entre NumExp
et Exp
dans getValue
.
Même l'échec suivant:
let getValue (nn : NumExp) = match nn with | Num(n) -> n
Est-il possible d'utiliser le même dans les deux cas, les syndicats discriminés qui fonctionne avec des fonctions? Les définitions DU eux-mêmes sont OK.
Je veux utiliser le même cas pour éviter d'ajouter un niveau d'indirection comme
type Exp =
| NumExpExp of NumExp
| Dot of NumExp * NumExp
| Op of string * Exp * Exp
dans la définition de Exp
.
Je sens que je manque quelque chose ici très basique.
La raison pour laquelle je NumExp
est que je veux être en mesure de « bouchon » 2 Exp
s dans un Dot
(au lieu de 2 flotteurs) car il rend plus facile générer des expressions, mais ils ne peut pas être Exp
, juste numérique.
EDIT : ce que je voulais vraiment savoir est de savoir si les deux cas, dans les deux pourraient être traités DDVP comme la même entité (un peu comme Exp
« y compris » NumExp
). Je me rends compte maintenant Exp.Num
et NumExp.Num
sont complètement entités distinctes. Tomas offre une belle façon de distinguer les deux cas ci-dessous.
La solution
Si vous avez deux syndicats discriminés avec des noms contradictoires des cas, vous pouvez utiliser le nom complet de l'affaire du syndicat discriminé:
let getValue (NumExp.Num(n)) = n
Un exemple plus complet ressemblerait à ceci:
let rec eval = function
| Exp.Num(f) -> f
| Exp.Dot(NumExp.Num(f1), NumExp.Num(f2)) ->
// whatever 'dot' represents
| Exp.Op(op, e1, e2) ->
// operator
Il utilise toujours les noms qualifiés, ce qui est probablement une bonne idée si les noms sont assez simples et il y a des cas contradictoires (ce qui pourrait conduire à une confusion).
EDIT: En ce qui concerne le partage des cas - il n'y a pas moyen automatique de le faire, mais vous pourriez avoir un cas dans votre Exp
qui inclut simplement les valeurs de NumExp
. Par exemple comme ceci:
type NumExp =
| Num of float
type Exp =
// first occurrence of NumExp is just a name, but F# allows us to reuse
// the name of the type, so we do that (you could use other name)
| NumExp of NumExp
// other cases
Lors de l'écriture fonction eval
alors vous écrire (notez que nous n'avons plus la question avec des conflits de noms, donc on n'a pas besoin des noms qualifiés):
| NumExp(Num f) -> f
| Op(op, e1, e2) -> // ...
Autres conseils
Lorsque cela est possible (par exemple en utilisant des variantes polymorphes en OCaml), vous pouvez faire beaucoup avec elle, mais (malheureusement) F # ne possède pas cette fonctionnalité de langage il est actuellement incapable d'exprimer ce que vous voulez utiliser des types syndicaux. Cependant, vous pouvez envisager d'utiliser à la place POO ...
Vous pouvez utiliser en remplacement . Cela ajoute un peu de frais généraux syntaxique, mais est la meilleure façon que j'ai trouvé de le faire.
type IExp = interface end
type NumExp =
| Num of float
interface IExp
type Exp =
| Dot of NumExp * NumExp
| Op of string * IExp * IExp
interface IExp
// This function accepts both NumExp and Exp
let f (x:IExp) = match x with
| :? NumExp as e -> match e with
| Num v -> "Num"
| :? Exp as e -> match e with
| Dot (e1,e2) -> "Dot"
| Op (op,e1,e2) -> "Op"
| _ -> invalidArg "x" "Unsupported expression type"
// This function accepts only NumExp
let g = function
| Num v -> "Num"
Juste une observation: Pourquoi avez-vous besoin les syndicats construits de cette façon
?J'aurais choisi une des deux options:
type NumExp = Num of float
type Exp =
| Num of float
| Dot of float * float
| Op of string * Exp * Exp
qui est plus simple, ou
type NumExp = Num of float
type Exp =
| NumExp
| Dot of float * float
| Op of string * Exp * Exp
Dans ce second cas, votre fonction
let getValue (Num(n) : NumExp) = n
fonctionne comme vous avez une définition de NumExp
maintenant.