2010-07-29 10 views
15

Decir que tengo una lista de formas:F #: ¿cómo seleccionar elegantemente y agrupar las uniones discriminadas?

type shape = 
| Circle of float 
| Rectangle of float * float 

let a = [ Circle 5.0; Rectangle (4.0, 6.0)] 

¿Cómo puedo comprobar a continuación, por ejemplo, un Círculo existe en a? Podría crear una función para cada forma

let isCircle s = 
    match s with 
    | Circle -> true 
    | _ -> false 
List.exists isCircle a 

pero siento que debe haber una manera más elegante en F #, aparte de tener que definir una función de este tipo para cada tipo de forma. ¿Esta ahí?

cuestión relacionada es cómo agrupar una lista de formas, basado en tipos de forma:

a |> seq.groupBy(<shapetype? >) 
+2

(ligeramente OT) Esto me recuerda, ya es hora de que [el resaltado de código sea compatible con F #] (http://meta.stackexchange.com/questions/58934/hight-time-for-code-highlighting-f-snippets) (!) – Abel

+0

Ver http://meta.stackexchange.com/questions/981/syntax-highlighting-hints no hay resaltado específico de idioma en SO. – Brian

Respuesta

7

Puede combinar F # reflexión con citas para obtener una solución genérica

type Shape = 
    | Circle of float 
    | Rectangle of float * float 

let isUnionCase (c : Expr<_ -> 'T>) = 
    match c with 
    | Lambdas (_, NewUnionCase(uci, _)) -> 
     let tagReader = Microsoft.FSharp.Reflection.FSharpValue.PreComputeUnionTagReader(uci.DeclaringType) 
     fun (v : 'T) -> (tagReader v) = uci.Tag 
    | _ -> failwith "Invalid expression" 

let a = 
    [ Circle 5.0; Rectangle (4.0, 6.0)] 
     |> List.filter (isUnionCase <@ Rectangle @>) 
printf "%A" a 
+0

La implementación de isUnionCase está muy por encima de mi cabeza, pero parece muy inteligente. Y usarlo es lo que sospechaba que podría ser. ¡Gracias! – Emile

+1

Parece que no puedo encontrar el espacio de nombre correcto. Microsoft.FSharp.Quotations es necesario para Expr pero no puedo encontrar Lambdas en ningún lado – Wouter

+0

'Lambda' (note, no ** s **) y' NewUnionCase' se pueden encontrar en el espacio de nombres 'Microsoft.FSharp.Quotations.Patterns'. – Ruxo

0

Una solución más elegante podría ser la siguiente:

let shapeExistsInList shapeType list = 
    List.exists (fun e -> e.GetType() = shapeType) list 

let circleExists = shapeExistsInList ((Circle 2.0).GetType()) a 

Sin embargo, no estoy muy Estoy satisfecho con esto porque tiene que crear una instancia de la unión discriminada para que funcione.

Agrupar por tipo de forma podría funcionar de manera similar.

+2

Esto funciona en este caso pero no para tipos más simples como 'tipo T = A | B' donde los casos * no * se implementan como tipos diferentes. – Mau

+0

Como nota adicional: El mismo problema, de tener que crear una instancia de clase para probar una instancia de clase, aparece todo el tiempo. Sería interesante ver algunas soluciones generales al problema. Puede valer la pena comenzar un hilo o una wiki. – TechNeilogy

+0

No sabía que .. –

16

Si usted está interesado en las diferentes categorías de formas, entonces tiene sentido definir otro tipo que exactamente los captura:

type shapeCategory = Circular | Rectangular 

let categorize = function 
    | Circle _ -> Circular 
    | Rectangle _ -> Rectangular 

List.exists ((=) Circular) (List.map categorize a) 

a |> Seq.groupBy(categorize) 

Editar - como se sugiere por Brian, puede utilizar alternativamente patrones activos vez de un nuevo tipo. Funciona de manera muy similar para sus ejemplos, pero se extendería mejor a patrones más complicados, mientras que el enfoque anterior puede ser mejor si el código a menudo funciona con las categorías, y desea un tipo de unión agradable para ellos en lugar de un tipo de elección. .

let (|Circular|Rectangular|) = function 
    | Circle _ -> Circular 
    | Rectangle _ -> Rectangular 

List.exists (function Circular -> true | _ -> false) a 

let categorize : shape -> Choice<unit, unit> = (|Circular|Rectangular|) 
a |> Seq.groupBy(categorize) 
+7

Alternativamente, un patrón activo. – Brian

+0

¿Podría dar un ejemplo de cómo sería eso? – Emile

+1

He agregado una versión con Active Patterns y una breve comparación. – RD1

8

Puede utilizar la biblioteca # reflexión F para obtener la etiqueta de un valor:

let getTag (a:'a) = 
    let (uc,_) = Microsoft.FSharp.Reflection.FSharpValue.GetUnionFields(a, typeof<'a>) 
    uc.Name 

a |> Seq.groupBy getTag 
+0

Solución muy agradable y genérica para la agrupación. ¡Gracias! – Emile

3

quiero añadir otra solución que funciona con las cotizaciones para cada caso de unión, basado en el desco proporcionado. Aquí va:

open Microsoft.FSharp.Quotations.Patterns 
open Microsoft.FSharp.Reflection 

let rec isUnionCase = function 
| Lambda (_, expr) | Let (_, _, expr) -> isUnionCase expr 
| NewTuple exprs -> 
    let iucs = List.map isUnionCase exprs 
    fun value -> List.exists ((|>) value) iucs 
| NewUnionCase (uci, _) -> 
    let utr = FSharpValue.PreComputeUnionTagReader uci.DeclaringType 
    box >> utr >> (=) uci.Tag 
| _ -> failwith "Expression is no union case." 

Definido de esta manera, isUnionCase funciona como desco ha demostrado, pero incluso en los casos del sindicato que están vacíos o tienen más de un valor. También puede ingresar una tupla de casos de unión separados por comas. Considere esto:

type SomeType = 
| SomeCase1 
| SomeCase2 of int 
| SomeCase3 of int * int 
| SomeCase4 of int * int * int 
| SomeCase5 of int * int * int * int 

let list = 
    [ 
     SomeCase1 
     SomeCase2 1 
     SomeCase3 (2, 3) 
     SomeCase4 (4, 5, 6) 
     SomeCase5 (7, 8, 9, 10) 
    ] 

list 
|> List.filter (isUnionCase <@ SomeCase4 @>) 
|> printfn "Matching SomeCase4: %A" 

list 
|> List.filter (isUnionCase <@ SomeCase3, SomeCase4 @>) 
|> printfn "Matching SomeCase3 & SomeCase4: %A" 

El primer isUnionCase que proporcioné solo funcionó para verificaciones de casos individuales. Más tarde agregué el control de expresión para NewTuple y pensé que te gustaría. Solo asegúrese de que si altera el código, las precomputaciones aún funcionan, por eso iucs se define fuera de la función anónima devuelta.

Cuestiones relacionadas