2017-01-27 1 views
4

фон:F # вложенных обобщенные типов, не совместимые с реализованным типом

Учитывая следующие два заявления в качестве F # программы:

  • типа A реализует интерфейс Wrapped<int>
  • типа B реализует интерфейс Wrapped<A>

Мы говорим, что тип A совместим с Wrapped<int> и тип B совместим с Wrapped<A> - совместим, насколько мне известно, что означает, что A можно передать в функцию, требующую Wrapped<int>.

Проблема:

Из моего опыта с программированием, я ожидал бы следующее быть правдой, учитывая приведенные выше два заявления:

  • типа B должен быть совместим с типом Wrapped<Wrapped<int>>

с B имеет A как параметр типа, где Wrapped<int> должен идти, и A и Wrapped<int> совместимы.

Это не тот случай. Следующая реализация:

type Wrapper<'a> = abstract member Value : 'a 

type A = 
    | A of int 

    interface Wrapper<int> with member this.Value = (let (A num) = this in num) 

type B = 
    | B of A 

    interface Wrapper<A> with member this.Value = (let (B a) = this in a) 


let f (x:Wrapper<Wrapper<int>>) = 
    x.Value.Value 

let testValue = f (B (A 1)) 

имеет ошибку компиляции на B (A 1) о том,

Тип B не совместим с типом Wrapper<Wrapper<int>>

Вопрос:

Так как я смог логически сделать Является ли я что-то не так, выполняя это? Или F # не имеет этой функции «вложенной совместимости», и если это так, есть ли особая причина для ее отсутствия?


Существует обходной путь к этому:

type B = 
    | B of A 

    interface Wrapper<Wrapper<int>> with member this.Value = (let (B a) = this in a :> Wrapper<int>) 

Это позволит устранить ошибку компиляции, хотя он чувствует себя немного не так. Я спрашиваю себя: «А что, если я когда-нибудь написать функцию для работы на Wrapper<A> типов? (Если я когда-либо добавить больше Wrapper<A> внедренцев)

+1

Ваши предположения неверны: в F # нет автоматического подтипирования (т. Е. Вы обычно не можете передавать подтип функции, ожидающей супертипа, если вы специально не объявляете эту функцию), и не всегда верно, что ' T :?> T 'когда' X:?> Y'. Это свойство называется «ковариация» (google it), и это не всегда выполняется. –

ответ

5

Функция вы просите ковариантен типов.

ковариации позволяет тип возвращаемого который является подтипом, а не точно определяемым параметром родового типа (а не тем, что это применимо только к интерфейсам, а не к конкретным типам).Это позволяет отключать IEnumerable<string> :?> IEnumerable<object> как string :?> object.

Декларация возможна на других языках .NET. Вот ваш пример в C#:

interface Wrapper<out T> { } 
class A : Wrapper<int> { } 
class B : Wrapper<A> { }   

var b = new B(); 
Action<Wrapper<Wrapper<int>>> unwrap = _ => { }; 
unwrap(b); //compiles 

F # не обеспечивает поддержку для объявления общековариантных типов, и не принуждать тип без явного объявления. Причиной этого является то, что ковариация приводит к деградированному типу.

Ковариантность в F # возможна с flexible types. Вот пример в F # на seq, который определяется как IEnumerable<out T>.

let s = [1..10] 
let r = s |> Seq.map(fun _ -> s) 

let print1 (v: seq<seq<int>>) = printfn "%A" v 
let print2 (v: seq<#seq<_>>) = printfn "%A" v 

print1 r //does not compile 
print2 r //compiles 

Есть вероятность, что вы можете сделать эту работу, если общие параметры были отмечены как ковариантные, так и используемые гибкие типы. Вы могли бы иметь объявления интерфейса в C# и ссылаться на сборку в F #.

Существует также mausch/VariantInterfaces, который изменяет сборку на основе соглашения об именах для добавления ковариантных/контравариантных объявлений, поэтому, если у вас есть объявления типа в отдельной сборке, вы можете запустить его в пост-сборке.