2010-03-26 13 views
13

... o, ¿cómo puedo filtrar una secuencia de clases por las interfaces que implementan?F # Equivalente a Enumerable.OfType <'a>

Digamos que tengo una secuencia de objetos que heredan de Foo, un seq<#Foo>. En otras palabras, mi secuencia contendrá una o más de cuatro subclases diferentes de Foo.

Cada subclase implementa una interfaz independiente diferente que no comparte nada con las interfaces implementadas por las otras subclases.

Ahora necesito filtrar esta secuencia solo a los elementos que implementan una interfaz en particular.

La versión C# es simple:

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); 
      } 
     } 
    } 

podría utilizar LINQ de F #:

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

Sin embargo, creo que debe haber una manera más idiomática para lograrlo. Pensé que esto iba a funcionar ...

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

Sin embargo, el compilador se queja de :? 'a - me dice:

Esta coerción tiempo de ejecución o el tipo de prueba de tipo 'b a' A implica un tipo indeterminado basado en la información anterior a este punto del programa. Las pruebas de tipo de tiempo de ejecución no están permitidas en algunos tipos. Se necesitan anotaciones de tipo adicionales.

No puedo averiguar qué tipo adicional de anotaciones agregar. No existe relación entre la interfaz 'a y #Foo, excepto que una o más subclases de Foo implementan esa interfaz. Además, no existe relación entre las diferentes interfaces que se pueden pasar como 'a, excepto que todas ellas están implementadas por subclases de Foo.

Espero impacientemente golpearme en la cabeza tan pronto como una de ustedes, amable, señale lo obvio que me he perdido.

Respuesta

6

Normalmente sólo añadir una 'caja' es suficiente (por ejemplo, cambiar function-fun x -> match box x with), pero voy a tratar de que fuera ...

Sí; Básicamente, no se puede transmitir lateralmente de un tipo genérico arbitrario a otro, pero se puede subir a Sistema.Objeto (a través de box) y luego abatido a lo que quiera:

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

Que hizo el truco. ¡Gracias! –

+1

@Brian - ¿pero seguirías esta ruta? En este caso, OfType parece mucho más bonito ... – Benjol

+0

Si solo se trata de sintaxis y no de rendimiento, sería bastante fácil extender el módulo Seq con una función 'ofType <'a>'. Entonces el cuerpo del método sería: 'pets |> Seq.ofType <'a> |> Seq.iter dest.Add' –

8

usted puede hacer esto:

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

'let ofType <'a> (items: _ seq) = items |> Seq.filter (fun x -> x:? 'A) |> Seq.cast <'a> 'me da * Esta coerción de tiempo de ejecución o prueba de tipo del tipo 'b a' a implica un tipo indeterminado basado en información anterior a este punto del programa. Las pruebas de tipo de tiempo de ejecución no están permitidas en algunos tipos. Se necesitan anotaciones de tipos adicionales. * Creo que en mi contexto actual es necesario el 'cuadro x' – Maslow

+1

añadiendo otro molde. Funciona:' 'let ofType <'a> (items: _ seq) = items |> Seq.cast |> Seq. filter (diversión x -> x:? 'a) |> Seq.cast <'a> \ –

2

De 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

Otra opción para los que se inclinan:

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

Tengo una biblioteca de código abierto disponible en Nuget, FSharp.Interop.Compose

que convierte la mayoría de los métodos Linq en un idomatic F# form. Incluyendo OfType

caso de prueba:

[<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] 
Cuestiones relacionadas