Общие случаи в 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
, просто числовое.
EDIT: я действительно хотел знать, могут ли два случая в двух DU рассматриваться как одна и та же сущность (например, Exp
", включая" NumExp
). Теперь я понимаю, что Exp.Num
и NumExp.Num
являются полностью отдельными объектами. Tomas обеспечивает хороший способ распознавания двух случаев ниже.
Ответы
Ответ 1
Если у вас есть два дискриминационных объединения с конфликтующими именами случаев, вы можете использовать полностью квалифицированное имя случая с дискриминационным соединением:
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
Это всегда использует полностью квалифицированные имена, что, вероятно, является хорошей идеей, если имена достаточно просты и существуют конфликтующие случаи (что может привести к путанице).
РЕДАКТИРОВАТЬ: Что касается обмена случаями - автоматического способа сделать это не существует, но в вашем 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) -> // ...
Ответ 2
Если это возможно (например, используя полиморфные варианты в OCaml), вы можете много сделать с ним, но (к сожалению) F # не имеет этой языковой функции, поэтому в настоящее время она неспособна выразить то, что вы хотите, используя типы union. Однако вы можете вместо этого использовать OOP...
Ответ 3
Вы можете использовать интерфейсы в качестве замены.
Это добавляет немного синтаксических накладных расходов, но это лучший способ, который я нашел для этого.
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"
Ответ 4
Просто наблюдение: зачем вам нужны союзы, построенные таким образом?
Я бы выбрал один из двух вариантов:
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
.