F # Эквивалент Enumerable.OfType <'a>

... или, как я могу фильтровать последовательность классов с помощью интерфейсов, которые они реализуют?

Скажем, у меня есть последовательность объектов, которые наследуются от Foo, a seq<#Foo>. Другими словами, моя последовательность будет содержать один или несколько из четырех разных подклассов Foo.

Каждый подкласс реализует другой независимый интерфейс, который не имеет ничего общего с интерфейсами, реализованными другими подклассами.

Теперь мне нужно отфильтровать эту последовательность только для элементов, реализующих определенный интерфейс.

Версия С# проста:

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

Я мог бы использовать LINQ из F #:

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

Однако я чувствую, что должен быть более идиоматический способ его достижения. Я думал, что это сработает...

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

Однако, компилятор жалуется на :? 'a - рассказывая мне:

Это принуждение или тест типа времени выполнения от типа "b" до "a" включает неопределенный тип на основе информации до этой точки программы. Тестирование типа времени выполнения не допускается для некоторых типов. Нужны дополнительные аннотации.

Я не могу понять, что добавить дополнительные аннотации типов. Нет никакой связи между интерфейсом 'a и #Foo, за исключением того, что один или несколько подклассов Foo реализуют этот интерфейс. Кроме того, нет никакой связи между различными интерфейсами, которые могут быть переданы как 'a, за исключением того, что все они реализованы подклассами Foo.

Я с нетерпением ожидаю, когда ударяю себя в голову, как только один из вас добрых людей укажет на очевидную вещь, которую я пропустил.

Ответы

Ответ 1

Обычно достаточно добавить "поле" (например, изменить function на fun x -> match box x with), но позвольте мне попробовать...

Да; в основном вы не можете отбрасывать бок с одного произвольного родового типа на другой, но вы можете повышать до System.Object(через box), а затем понижать до всего, что вам нравится:

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

Ответ 2

Вы можете сделать это:

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

Ответ 3

Из 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)
   }

Ответ 4

Другой вариант для наклонных:

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

Ответ 5

У меня есть библиотека с открытым исходным кодом, доступная на nuget, FSharp.Interop.Compose

Что конвертирует большинство методов Linq в idomatic F # form. Включая OfType

Тестовый пример:

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