开发者

Shared cases in F# discriminated unions

开发者 https://www.devze.com 2023-01-05 20:19 出处:网络
I want to write something like this: type NumExp = Num of float type Exp = Num of float Dot of NumExp * NumExp

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.


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号