F # и ковариация интерфейса: что делать? (в частности, seq <> aka IEnumerable <>) - PullRequest
12 голосов
/ 17 ноября 2010

Я пытаюсь вызвать метод .NET, принимающий универсальный IEnumerable<T> от F # с использованием seq<U>, такой, что U является подклассом T. Это не работает так, как я ожидал, будет:

Со следующим простым принтером:

let printEm (os: seq<obj>) = 
    for o in os do
        o.ToString() |> printfn "%s"

Вот результаты, которые я получаю:

Seq.singleton "Hello World"  |> printEm // error FS0001; 
//Expected seq<string> -> 'a but given seq<string> -> unit

Seq.singleton "Hello World"  :> seq<obj> |> printEm // error FS0193;
//seq<string> incompatible with seq<obj>

Seq.singleton "Hello World"  :?> seq<obj> |> printEm // works!

Seq.singleton 42 :> seq<obj> |> printEm // error FS0193
Seq.singleton 42 :?> seq<obj> |> printEm // runtime InvalidCastException!
//Unable to cast object of type 'mkSeq@541[System.Int32]'
// to type 'System.Collections.Generic.IEnumerable`1[System.Object]'.

В идеале, я бы хотел, чтобы первый синтаксис работал - или что-то максимально приближенное к нему, с проверкой типов во время компиляции. Я не понимаю, где компилятор находит функцию seq<string> -> unit в этой строке, но, очевидно, ковариация для IEnumerable не работает, и это как-то приводит к этому сообщению об ошибке. Использование явного приведения приводит к разумному сообщению об ошибке - но оно также не работает. Использование приведений во время выполнения - но только для строк, целые числа терпят неудачу с исключением (неприятно).

Я пытаюсь взаимодействовать с другим кодом .NET; Вот почему мне нужны определенные типы IEnumerable.

Какой самый чистый и желательно эффективный способ приведения ко-или контравариантных интерфейсов, таких как IEnumerable в F #?

Ответы [ 2 ]

10 голосов
/ 17 ноября 2010

К сожалению, F # не поддерживает co \ contravariance.Вот почему это

Seq.singleton "Hello World"  :> seq<obj> |> printEm 

не работает

Вы можете объявить параметр как seq <_> или ограничить набор типов параметров некоторым конкретным семейством, используя гибкий тип s (с хеш-кодом), это исправит этот сценарий:

let printEm (os: seq<_>) = 
for o in os do
    o.ToString() |> printfn "%s"

Seq.singleton "Hello World"  |> printEm 

Учитывая следующие строки:

Seq.singleton 42 :> seq<obj> |> printEm // error FS0193
Seq.singleton 42 :?> seq<obj> |> printEm

Дисперсия работает только для классов, поэтому аналогичноКод также не будет работать в C #.

Вы можете попробовать привести элементы последовательности к требуемой простоте типа через Seq.cast

8 голосов
/ 17 ноября 2010

Используйте для этого Seq.cast.Например:

Seq.singleton "Hello World"  |> Seq.cast |> printEm

По общему признанию, это исключает безопасность типа:

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

let printEm (os: seq<Dog>) =  
    for o in os do 
        o.ToString() |> printfn "%s" 

Seq.singleton (Beagle())  |> Seq.cast |> printEm // ok
Seq.singleton (Animal())  |> Seq.cast |> printEm // kaboom!

, но это целесообразно.

В качестве альтернативы вы можете использовать гибких типов :

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

let printEm (os: seq<#Dog>) =  // note #Dog
    for o in os do 
        o.ToString() |> printfn "%s" 

Seq.singleton (Beagle()) |> printEm // ok
Seq.singleton (Animal()) |> printEm // type error

, что является просто сокращением для универсальных "типов полей 'a when 'a :> Dog".

И, наконец, вы всегда можете отобразить upcast, например,

let printEm (os: seq<obj>) =  
    for o in os do 
        o.ToString() |> printfn "%s" 

Seq.singleton "Hello" |> Seq.map box |> printEm // ok

, где box повышается до obj.

...