Общие случаи в F# размеченные объединения

я хочу написать что-то вроде этого:

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

компилятор жалуется на конфликт между NumExp и Exp на getValue. Даже следующее не удается:

let getValue (nn : NumExp) = match nn with | Num(n) -> n

есть ли способ использовать один и тот же случай в обоих дискриминируемых объединениях, которые работают с функциями? сами определения DU в порядке.

я хочу использовать тот же случай, чтобы избежать добавляя уровень косвенности как

type Exp =
    | NumExpExp of NumExp
    | Dot of NumExp * NumExp
    | Op of string * Exp * Exp

на Exp определение. Я чувствую, что упускаю что-то очень важное.

я NumExp это то, что я хочу иметь возможность "подключить" 2 Expна Dot (а не 2 поплавка), потому что это упрощает создание выражений, но они не могут быть никакими Exp, просто числовые.

редактировать: что я действительно хотел знать, могут ли два случая в двух DUs рассматриваться как одна и та же сущность (вроде как Exp "в том числе" NumExp). Теперь я понимаю!--13--> и NumExp.Num совершенно разные лица. Tomas предоставляет хороший способ различения двух случаев ниже.

4 ответов


если у вас есть два дискриминируемых союза с конфликтующими именами случаев, вы можете использовать полное имя случая дискриминируемого Союза:

 let getValue (NumExp.Num(n)) = n  

более полный пример будет выглядеть так:

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

это всегда использует полные имена, что, вероятно, хорошая идея, если имена достаточно просты и есть конфликтующие случаи (которые могут привести к путанице).

EDIT: в обмене случаях - нет автоматический способ сделать это, но у вас может быть случай в вашем Exp это просто включает в себя значения NumExp. Например, вот так:

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

при написании eval функция, которую вы затем напишете (обратите внимание, что у нас больше нет проблемы с столкновениями имен, поэтому нам не нужны полные имена):

| NumExp(Num f) -> f
| Op(op, e1, e2) -> // ...

когда это возможно (например, использование полиморфных вариантов в OCaml), вы можете многое с ним сделать, но (к сожалению) F# не имеет этой языковой функции, поэтому в настоящее время он не способен выражать то, что вы хотите, используя типы объединения. Однако вместо этого вы можете использовать OOP...


можно использовать интерфейсы в качестве замены. Это добавляет немного синтаксических накладных расходов, но это лучший способ, который я нашел для этого.

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"

просто наблюдение: зачем вам нужны союзы, построенные таким образом?

Я бы выбрал один из двух вариантов:

type NumExp = Num of float

type Exp =
    | Num of float
    | Dot of float * float
    | Op of string * Exp * Exp

что более просто, или

type NumExp = Num of float

type Exp =
    | NumExp
    | Dot of float * float
    | Op of string * Exp * Exp

в этом втором случае, ваша функция

let getValue (Num(n) : NumExp) = n

работает как у вас есть одно из определений NumExp сейчас.