Подтвердить что ты не робот

F # и ковариация интерфейса: что делать? (особенно seq <> aka IEnumerable <>)

Я пытаюсь вызвать метод .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 '[email protected][System.Int32]'
// to type 'System.Collections.Generic.IEnumerable`1[System.Object]'.

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

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

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

4b9b3361

Ответ 1

Используйте 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.

Ответ 2

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

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

не работает

Вы можете объявить параметр как seq < _ > или limit набор типов параметров для определенного семейства, используя гибкий тип 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

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

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