2010-07-29 11 views
15

Dire che ho una lista di forme:F #: come selezionare e raggruppare elegantemente i sindacati discriminati?

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

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

Come posso quindi verificare per esempio un cerchio esiste in un? Potrei creare una funzione per ogni forma

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

ma sento ci deve essere un modo più elegante in F #, oltre ad avere a definire tale funzione per ogni tipo di forma. È lì?

questione connessa è come gruppo un elenco di forme, in base ai tipi di forma:

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

(leggermente OT) Questo mi ricorda, molto tempo che [l'evidenziazione del codice è supportata per F #] (http://meta.stackexchange.com/questions/58934/hight-time-for-code-highlighting-f-snippets) (!) – Abel

+0

Vedere http://meta.stackexchange.com/questions/981/syntax-highlighting-hints su SO non è presente un'evidenziazione specifica della lingua. – Brian

risposta

7

è possibile combinare F # riflessione con citazioni per ottenere soluzione generica

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

L'implementazione di isUnionCase è molto al di sopra della mia testa, ma sembra molto intelligente. E usarlo è sulla falsariga sospetto che potrebbe sembrare. Grazie! – Emile

+1

Non riesco a trovare lo spazio dei nomi corretto. Microsoft.FSharp.Quotations è necessario per Expr ma non riesco a trovare Lambda ovunque – Wouter

+0

'Lambda' (nota, non ** s **) e' NewUnionCase' possono essere trovati nello spazio dei nomi 'Microsoft.FSharp.Quotations.Patterns'. – Ruxo

0

Una soluzione più elegante potrebbe essere la seguente:

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

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

Tuttavia, io non sono molto soddisfatto di questo da solo perché devi creare un'istanza dell'unione discriminata perché funzioni.

Il raggruppamento per tipo di forma potrebbe funzionare in modo simile.

+2

Funziona in questo caso ma non per tipi più semplici come 'tipo T = A | B' dove i casi * non * sono implementati come tipi diversi. – Mau

+0

Per inciso: lo stesso problema - di dover creare un'istanza di classe per testare un'istanza di classe - si presenta sempre. Sarebbe interessante vedere alcune soluzioni generali al problema. Potrebbe valere la pena iniziare una discussione o una wiki. – TechNeilogy

+0

Non lo sapevo .. –

16

Se siete interessati nelle diverse categorie di forme, allora ha senso per definire un altro tipo che cattura esattamente li:

type shapeCategory = Circular | Rectangular 

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

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

a |> Seq.groupBy(categorize) 

Edit - come suggerito da Brian, in alternativa è possibile utilizzare i modelli attivi invece di un nuovo tipo. Funziona in modo abbastanza simile per i tuoi esempi, ma si estenderebbe meglio a schemi più complicati, mentre l'approccio sopra potrebbe essere migliore se il codice spesso funziona con le categorie e vuoi un bel tipo di unione per loro invece di un tipo di scelta .

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

In alternativa, un modello attivo. – Brian

+0

Puoi dare un esempio di come sarebbe? – Emile

+1

Ho aggiunto una versione con Pattern attivi e un breve confronto. – RD1

8

È possibile utilizzare la libreria # riflessione F per ottenere tag di un valore:

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

a |> Seq.groupBy getTag 
+0

Soluzione molto bella e generica per il raggruppamento. Grazie! – Emile

3

voglio aggiungere un altro soluzione che funziona con le quotazioni per ogni caso sindacale, in base a quello desco fornito. Qui si 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." 

Definito in questo modo, isUnionCase funziona come desco ha dimostrato, ma anche su casi sindacali che sono vuoti o hanno più di un valore. Puoi anche inserire una tupla di casi di unione separati da virgola. Considera questo:

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" 

Il primo isUnionCase che ho fornito ha funzionato solo per i singoli casi. In seguito ho aggiunto l'espressione check per NewTuple e ho pensato che ti sarebbe piaciuto. Assicurati che se modifichi il codice, le precomputazioni funzionano ancora, questo è il motivo per cui iucs è definito al di fuori della funzione anonima restituita.

Problemi correlati