2013-09-04 12 views
5

Ho il seguente:Come mappare una sequenza di unioni discriminate (in cui tutti gli elementi sono dello stesso caso) su una sequenza di elementi del tipo del caso?

type union1 = 
    | Case1 of string 
    | Case2 of int 

let union1s = seq { for i in 1..5 do yield case2 i } 

Come cambio union1s ad una sequenza di tipo seq<int>?

Qualcosa di simile:

let matchCase item = 
    match item with 
    | Case1 x -> x 
    | Case2 x -> x 

let case2s = Seq.map matchCase union1s 

Questo tentativo non funziona perché matchcase non può restituire due tipi diversi.

Le risposte suggerite hanno lo stesso problema (se ho capito bene)

let matchCaseOpt = function 
    | Case1 x -> Some x 
    | Case2 x -> Some x 
    | _ -> None 

let case2s = Seq.choose matchCaseOpts unions1s 

L'espressione Alcuni x aspetta aspetta che tipo stringa di opzione nella partita per Case2

ho risolto il mio particolare caso d'uso usando una DU di sequenze.

type Union1s = 
    | Case1s of seq<string> 
    | Case2s of seq<int>  
+0

Si prega di notare che non è una risposta suggerita. Case1 è sbagliato, dovrebbe essere None. – Gustavo

risposta

2

si potrebbe provare la seguente, l'implementazione di uso generale riflessione-based:

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

let filterUnionCases (branch : Expr<'T -> 'Union>) (inputs : 'Union list) = 
    let rec getUnionCase (e : Expr) = 
     match e with 
     | NewUnionCase(unionCaseInfo,_) -> unionCaseInfo 
     | Lambda(_, body) -> getUnionCase body 
     | Let(_, TupleGet _, body) -> getUnionCase body 
     | _ -> invalidArg "branch" "not a union case constructor" 

    let getBranchContents (uci : UnionCaseInfo) (u : 'Union) = 
     let uci', fields = FSharpValue.GetUnionFields(u, typeof<'Union>) 
     if uci = uci' then 
      match fields with 
      | [| field |] -> field :?> 'T 
      | _ -> FSharpValue.MakeTuple(fields, typeof<'T>) :?> 'T 
      |> Some 
     else None 

    let uci = getUnionCase branch 
    inputs |> List.choose (getBranchContents uci) 


filterUnionCases <@ Case1 @> [ Case1 "string1" ; Case2 2 ; Case1 "string2" ] // [ "string1" ; "string2" ] 
filterUnionCases <@ Case2 @> [ Case1 "string1" ; Case2 2 ; Case1 "string2" ] // [ 2 ] 

questo dovrebbe funzionare anche nei casi sindacali che contengono più campi.

+0

Grazie. Posso seguire quello che sta succedendo, ma sicuramente allunga quello che sto usando ora. C'è da imparare – Remko

+0

Dovresti tenere a mente che questo è * lento *, poiché usa la riflessione. Se le prestazioni sono importanti per te, dovresti cercare le soluzioni offerte dagli altri poster. – eirik

+0

Lo farò, ho aggirato il mio problema usando una DU di sequenze. Grazie ancora – Remko

8

si stanno assumendo la sequenza non contiene una sola case1, quindi, se questo non è vero è necessario generare un'eccezione.

let matchCase item = 
    match item with 
    | Case1 x -> failwith "Unexpected Case1" 
    | Case2 x -> x 

let case2s = Seq.map matchCase union1s 

Un approccio alternativo, se non si è certi della sequenza contiene sempre lo stesso caso è quello di utilizzare un'Opzione

let matchCase item = 
    match item with 
    | Case1 x -> None 
    | Case2 x -> Some x 

poi dipende da come si gestirà questi casi, si può solo filtrare i valori None utilizzando Seq.choose anziché Seq.map come mostrato nell'altra risposta.

Quale approccio da seguire dipende se si considera di avere un argomento con Case1 un caso eccezionale o parte della logica del programma. C'è stata una domanda su questo F#: Some, None, or Exception? di recente.

L'utilizzo di un DU di sequenze è corretto se non si mescolano i casi, in questo modo il tipo DU limita il dominio ai casi reali.

+0

Ciao, grazie. Tuttavia a volte il seq contiene elementi di solo caso1 – Remko

+0

Quindi utilizzare la versione alternativa e controllare i risultati. Puoi decidere quindi cosa fare con i NONE. Possono essere filtrati con Seq.choose come mostrato nell'altra risposta.Dipende davvero se si tratta di un caso Eccezionale o meno. – Gustavo

+0

domanda aggiornata – Remko

3

In alternativa:

let matchCaseOpt item = 
    match item with 
    | Case2 x -> Some(x) 
    | _ -> None 

let case2s = union1s |> Seq.choose matchCaseOpt 

Questa versione scenderà altri casi oltre Case2, in cui la soluzione di Gustavo sarà un'eccezione se quelli si verificano. Quale soluzione è la migliore dipende ovviamente dalle tue esigenze.

Si noti che questa soluzione utilizza Seq.choose anziché Seq.map.

Problemi correlati