6

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 Exps 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.

Mau
  • 14,234
  • 2
  • 31
  • 52

4 Answers4

14

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) -> // ...
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • Thanks Tomas, the disambiguation works wonders. What I was asking, though was more like 'can the two cases in the two DUs be treated as the same entity?', or in other words, 'can Exp "include" NumExp?'. But from your answer, the answer is no. Thanks! – Mau Jul 08 '10 at 10:57
  • 2
    @Mau: I added some information about sharing of the cases between different discriminated unions. It is not possible, but you can include one in the other. – Tomas Petricek Jul 08 '10 at 16:14
3

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"
dtech
  • 13,741
  • 11
  • 48
  • 73
  • 1
    Uhmm I'm not sure I follow. How does this solve the issue of *using the `Num` literal in both unions*? – Mau Feb 17 '15 at 10:09
  • 1
    You can use `IExp` when you want to use both DU's (combined 3 cases), and `NumExp` when you want to use only `Num`. The `f` function in an example of a function that uses/accepts all 3 cases. Essentially you've made `IExp` the DU with 3 cases, and `Exp` is just there in the background. – dtech Feb 17 '15 at 10:33
2

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...

J D
  • 48,105
  • 13
  • 171
  • 274
-1

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.

Muhammad Alkarouri
  • 23,884
  • 19
  • 66
  • 101
  • Thanks Muhammad. The first solution is excluded in the question above (harder to deal with in the rest of the code). The second solution is syntactically correct and semantically what I would like, but produces a different data structure from what I need (`Exp.NumExp` is just an empty case, not a field of type `NumExp`). – Mau Jul 08 '10 at 11:03