2010-07-07 14 views
6

que quiero escribir algo como esto:casos compartidos en F # discriminados sindicatos

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 

El compilador se queja de un conflicto entre NumExp y Exp en getValue. Incluso la siguiente falla:

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

¿Hay una manera de utilizar el mismo caso en ambas uniones discriminadas que trabaja con funciones? Las definiciones de DU están en buen estado.

Quiero usar el mismo caso para evitar la adición de un nivel de indirección como

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

en la definición Exp. Siento que me estoy perdiendo algo muy básico aquí.

La razón por la que tengo NumExp es que quiero ser capaz de 'tapón' 2 Exp s en un Dot (en lugar de 2 flotadores), ya que hace más fácil la generación de expresiones, pero no puedo ser cualquier Exp, simplemente numérica .

EDITAR: lo que realmente quería saber es si los dos casos en los dos usuarios intermedios podrían ser tratados como la misma entidad (algo así como Exp "incluyendo" NumExp). Me doy cuenta ahora que Exp.Num y NumExp.Num son entidades completamente separadas. Tomás proporciona una buena manera de discriminar los dos casos a continuación.

Respuesta

13

Si tiene dos uniones discriminados con nombres conflictivos de los casos, se puede utilizar el nombre completo de la caja de unión discriminado:

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

Un ejemplo más completo sería el siguiente:

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 

Esto siempre utiliza nombres completamente calificados, lo cual es una buena idea si los nombres son lo suficientemente simples y si hay casos conflictivos (lo que podría generar confusión).

EDIT: relacionado con el intercambio de los casos - no hay forma automática de hacer eso, sino que podría tener un caso en su Exp que simplemente incluye valores de NumExp. Por ejemplo así:

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 

Al escribir eval función A continuación, escribir (tenga en cuenta que ya no tenemos el problema con las colisiones de nombres, por lo que no necesitamos nombres completos):

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

Gracias Tomas, la desambiguación funciona de maravilla. Sin embargo, lo que estaba preguntando era más bien '¿pueden los dos casos en los dos DU ser tratados como la misma entidad?', O en otras palabras, 'puede Exp' incluir 'NumExp?'. Pero de su respuesta, la respuesta es no. ¡Gracias! – Mau

+2

@Mau: He añadido información sobre el intercambio de casos entre diferentes sindicatos discriminados. No es posible, pero puede incluir uno en el otro. –

+0

gracias, eso es exactamente lo que había hecho :-) – Mau

-1

Solo una observación: ¿Por qué necesita los sindicatos construidos de esta manera?

yo hubiera elegido una de dos opciones:

type NumExp = Num of float 

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

que es la más sencilla, o

type NumExp = Num of float 

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

En este segundo caso, su función

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

obras como usted tiene una definición de NumExp ahora.

+0

Gracias Muhammad. La primera solución se excluye en la pregunta anterior (más difícil de tratar en el resto del código). La segunda solución es sintácticamente correcta y semánticamente lo que me gustaría, pero produce una estructura de datos diferente de la que necesito ('Exp.NumExp' es solo un caso vacío, no un campo de tipo' NumExp'). – Mau

2

Cuando eso es posible (por ejemplo, utilizando variantes polimórficas en OCaml), puede hacer mucho con él pero (lamentablemente) F # no tiene esta función de idioma, por lo que actualmente no puede expresar lo que desea utilizando tipos de unión. Sin embargo, puede considerar usar OOP en su lugar ...

2

Puede usar interfaces as a substitute. Esto agrega un poco de sobrecarga sintáctica, pero es la mejor forma que he encontrado para hacer esto.

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" 
+1

Uhmm No estoy seguro de seguir. ¿Cómo resuelve esto el problema de * usar el literal 'Num' en ambas uniones *? – Mau

+1

Puede usar 'IExp' cuando desee usar ambos DU (combinados 3 casos), y' NumExp' cuando desee usar solamente 'Num'. La función 'f' en un ejemplo de una función que usa/acepta los 3 casos. Esencialmente has hecho 'IExp' el DU con 3 casos, y' Exp' está ahí en el fondo. – dtech

+1

¡Ya veo! ¡Ciertamente agradable! – Mau