开发者

F# Equivalent to Enumerable.OfType<'a>

...or, how do I filter a sequence of classes by the interfaces they implement?

Let's say I have a sequence of objects that inherit from Foo, a seq<#开发者_如何学运维Foo>. In other words, my sequence will contain one or more of four different subclasses of Foo.

Each subclass implements a different independent interface that shares nothing with the interfaces implemented by the other subclasses.

Now I need to filter this sequence down to only the items that implement a particular interface.

The C# version is 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);
            }
        }
    }

I could use LINQ from F#:

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

However, I feel like there should be a more idiomatic way to accomplish it. I thought this would work...

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

However, the complier complains about :? 'a - telling me:

This runtime coercion or type test from type 'b to 'a involves an indeterminate type based on information prior to this program point. Runtime type tests are not allowed on some types. Further type annotations are needed.

I can't figure out what further type annotations to add. There's no relationship between the interface 'a and #Foo except that one or more subclasses of Foo implement that interface. Also, there's no relationship between the different interfaces that can be passed in as 'a except that they are all implemented by subclasses of Foo.

I eagerly anticipate smacking myself in the head as soon as one of you kind people points out the obvious thing I've been missing.


You can do this:

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


Typically just adding a 'box' is sufficient (e.g. change function to fun x -> match box x with), but let me try it out...

Yeah; basically you cannot sideways cast from one arbitrary generic type to another, but you can upcast to System.Object (via box) and then downcast to anything you like:

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")


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


Another option for those inclined:

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


I have an open source library available on nuget, FSharp.Interop.Compose

That Converts most Linq methods into a idomatic F# form. Including 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]
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜