Общие случаи в 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
сейчас.