2010-03-26 19 views
13

... o, come si filtra una sequenza di classi dalle interfacce che implementano?F # Equivalente a Enumerable.OfType <'a>

Diciamo che ho una sequenza di oggetti che ereditano da Foo, uno seq<#Foo>. In altre parole, la mia sequenza conterrà una o più di quattro diverse sottoclassi di Foo.

Ciascuna sottoclasse implementa un'interfaccia indipendente diversa che non condivide nulla con le interfacce implementate dalle altre sottoclassi.

Ora ho bisogno di filtrare questa sequenza solo per gli elementi che implementano una particolare interfaccia.

Il C# versione è semplice:

void MergeFoosIntoList<T>(IEnumerable<Foo> allFoos, IList<T> dest) 
     where T : class 
    { 
     foreach (var foo in allFoos) 
     { 
      var castFoo = foo as T; 
      if (castFoo != null) 
      { 
       dest.Add(castFoo); 
      } 
     } 
    } 

potevo usare LINQ da F #:

let mergeFoosIntoList (foos:seq<#Foo>) (dest:IList<'a>) = 
      System.Linq.Enumerable.OfType<'a>(foos) 
      |> Seq.iter dest.Add 

Tuttavia, mi sento come ci dovrebbe essere un modo più idiomatico per realizzarlo. Ho pensato che questo avrebbe funzionato ...

let mergeFoosIntoList (foos:seq<#Foo>) (dest:IList<'a>) = 
      foos 
      |> Seq.choose (function | :? 'a as x -> Some(x) | _ -> None) 
      |> Seq.iter dest.Add 

Tuttavia, il compilatore lamenta :? 'a - dicendomi:

Questa coercizione runtime o tipo di test dal tipo 'b per' un comporta un tipo indeterminata base sulle informazioni prima di questo punto del programma. I test del tipo runtime non sono consentiti su alcuni tipi. Sono necessarie ulteriori annotazioni sul tipo.

Non riesco a capire quali ulteriori annotazioni di tipo aggiungere. Non esiste alcuna relazione tra l'interfaccia 'a e #Foo eccetto che una o più sottoclassi di Foo implementano tale interfaccia. Inoltre, non esiste alcuna relazione tra le diverse interfacce che possono essere passate come 'a eccetto che sono tutte implementate da sottoclassi di Foo.

Prevedo con impazienza di schiaffeggiarmi in testa non appena uno di voi gentili persone sottolinea la cosa ovvia che mi è mancata.

risposta

6

In genere solo l'aggiunta di una 'scatola' è sufficiente (per esempio cambiare function-fun x -> match box x with), ma mi permetta di provare it out ...

Sì; in pratica non è possibile eseguire il cast laterale da un tipo generico arbitrario a un altro, ma è possibile eseguire l'upcast su System.Object (via box) e poi abbattuto per qualcosa che ti piace:

type Animal() = class end 
type Dog() = inherit Animal() 
type Cat() = inherit Animal() 

let pets : Animal list = 
    [Dog(); Cat(); Dog(); Cat(); Dog()] 
printfn "%A" pets 

open System.Collections.Generic  

let mergeIntoList (pets:seq<#Animal>) (dest:IList<'a>) = 
    pets 
    |> Seq.choose (fun p -> match box p with 
          | :? 'a as x -> Some(x) | _ -> None) //' 
    |> Seq.iter dest.Add 

let l = new List<Dog>() 
mergeIntoList pets l 
l |> Seq.iter (printfn "%A") 
+0

Che ha fatto il trucco. Grazie! –

+1

@Brian - ma andresti su questa strada? In questo caso, OfType sembra molto più bello ... – Benjol

+0

Se si tratta solo di sintassi e non di prestazioni, sarebbe abbastanza semplice estendere il modulo Seq con una funzione 'ofType <'a>'. Quindi il corpo del metodo sarebbe semplicemente: 'pets |> Seq.ofType <'a> |> Seq.iter dest.Add' –

8

Si può fare questo:

let foos = candidates |> Seq.filter (fun x -> x :? Foo) |> Seq.cast<Foo> 
+0

'let diType <'a> (elementi: _ seq) = elementi |> Seq.filter (fun x -> x:? 'A) |> Seq.cast <'a> 'mi dà * Questa runtime coercizione o tipo test da tipo 'b a' a implica un tipo indeterminato basato su informazioni precedenti a questo punto del programma. I test del tipo runtime non sono consentiti su alcuni tipi. Sono necessarie ulteriori annotazioni di tipo. * Penso che nel mio contesto corrente sia necessario 'box x' – Maslow

+1

aggiungendo un altro cast funziona:' 'let diType <'a> (items: _ seq) = items |> Seq.cast |> Seq. filtro (fun x -> x:? 'a) |> Seq.cast <'a> \ –

2

Da https://gist.github.com/kos59125/3780229

let ofType<'a> (source : System.Collections.IEnumerable) : seq<'a> = 
    let resultType = typeof<'a> 
    seq { 
     for item in source do 
     match item with 
      | null ->() 
      | _ -> 
       if resultType.IsAssignableFrom (item.GetType()) 
       then 
        yield (downcast item) 
    } 
1

Un'altra opzione per coloro che optano:

Module Seq = 
    let ofType<'a> (items: _ seq)= items |> Seq.choose(fun i -> match box i with | :? 'a as a -> Some a |_ -> None) 
0

Possiedo una libreria open source disponibile su nuget, FSharp.Interop.Compose

Converte più metodi Linq in idomatic F# form. Compreso OfType

Test Case:

[<Fact>] 
let ofType() = 
    let list = System.Collections.ArrayList() 
    list.Add(1) |> ignore 
    list.Add("2") |> ignore 
    list.Add(3) |> ignore 
    list.Add("4") |> ignore 
    list 
     |> Enumerable.ofType<int> 
     |> Seq.toList |> should equal [1;3] 
Problemi correlati