Shared cases in F# discriminated unions
I want to write something like this:
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
The compiler complains about a conflict between NumExp
and Exp
in getValue
.
Even the following fails:
let getValue (nn : NumExp) = match nn with | Num(n) -> n
Is there a way to use the same case in both discriminated unions that works with functions? The DU definitions themselves are OK.
I want to use the same case to avoid adding a level of indirection 开发者_开发百科like
type Exp =
| NumExpExp of NumExp
| Dot of NumExp * NumExp
| Op of string * Exp * Exp
in the Exp
definition.
I feel I'm missing something very basic here.
The reason I have NumExp
is that I want to be able to 'plug' 2 Exp
s into a Dot
(rather than 2 floats) because it makes generating expressions easier, but they can't be any Exp
, just numerical.
EDIT: what I really wanted to know is whether the two cases in the two DUs could be treated as the same entity (kind of like Exp
"including" NumExp
). I realise now Exp.Num
and NumExp.Num
are completely separate entities. Tomas provides a nice way of discriminating the two cases below.
If you have two discriminated unions with conflicting names of cases, you can use fully qualified name of the discriminated union case:
let getValue (NumExp.Num(n)) = n
A more complete example would look like this:
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
This always uses fully qualified names, which is probably a good idea if the names are simple enough and there are conflicting cases (which could lead to a confusion).
EDIT: Regarding sharing of cases - there is no automatic way of doing that, but you could have a case in your Exp
that simply includes values of NumExp
. For example like this:
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
When writing eval
function you would then write (note that we no longer have the issue with name clashes, so we don't need fully qualified names):
| NumExp(Num f) -> f
| Op(op, e1, e2) -> // ...
You can use interfaces as a substitute. This adds a bit of syntactic overhead, but is the best way I've found to do this.
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"
When that is possible (e.g. using polymorphic variants in OCaml), you can do a lot with it but (sadly) F# does not have this language feature so it is currently incapable of expressing what you want using union types. However, you might consider using OOP instead...
Just an observation: Why do you need the unions constructed this way?
I would have chosen one of two options:
type NumExp = Num of float
type Exp =
| Num of float
| Dot of float * float
| Op of string * Exp * Exp
which is the more simple, or
type NumExp = Num of float
type Exp =
| NumExp
| Dot of float * float
| Op of string * Exp * Exp
In this second case, your function
let getValue (Num(n) : NumExp) = n
works as you have one definition of NumExp
now.
精彩评论