Difference between treatment of ambiguous generic interfaces by F# between VS 2012 and VS 2015 leading to compile errors in the latter

瘦欲@ 提交于 2019-12-22 17:56:53

问题


(Note: Question updated with full reproducible example)

After migrating an F# project from VS 2012 to VS 2015 I receive an error on certain usages of interfaces. In particular it happens where a type implements two generic interfaces. I know this is not allowed in F# directly, but this type comes from C#.

To reproduce the problem:

1. type definition in C#:

Paste this in some class library.

public interface Base { }

public interface IPrime<T> : Base
{
    T Value { get; }
}


public interface IFloat : IPrime<double>
{
}

public interface IInt : IFloat, IPrime<int>
{
    int Salary { get; }
}

public abstract class Prime<T> : IPrime<T>
{
    public T Value { get; protected internal set; }

    public static implicit operator T(Prime<T> value)
    {
        return value.Value;
    }
}


public class FFloat : Prime<double>, IFloat
{
    public FFloat(double value)
    {
        this.Value = value;
    }
    public double Salary { get; set; }
}

public class FInt : Prime<int>, IInt
{
    public FInt(int value)
    {
        this.Value = value;
    }
    public int Salary { get; set; }
    int IPrime<int>.Value { get { return this.Value; } }
    double IPrime<double>.Value { get { return this.Value; } }
}

2. Usage of types in F#:

Usage in Visual Studio 2012, working:

open SomeClassLib

[<EntryPoint>]
let main argv = 
    let i = new FInt(10)
    let f = new FFloat(12.0)
    let g = fun (itm: SomeClassLib.Base) -> 
        match itm with
        | :? IInt as i -> i.Value
        | :? IFloat as i -> i.Value |> int
        | _ -> failwith "error"

If you open the same solution in Visual Studio 2015 you get the error

error FS0001: Type mismatch. Expecting a float -> float but given a float -> int.The type 'float' does not match the type 'int'

This is of course easily corrected by a typecast, but then, surprise surprise, it won't load in Visual Studio 2012 anymore (well, there are ways to get it working in both, and in this example it is trivial).

3. Findings

If you hover over the i.Value you get:

Visual Studio 2012

| :? IInt as i -> i.Value          // Value is int
| :? IFloat as i -> i.Value |> int // Value is float

Visual Studio 2015

| :? IInt as i -> i.Value          // Value is float
| :? IFloat as i -> i.Value |> int // Value is float

I'm wondering where this difference comes from.

Questions / remarks

I find it strange that the compiler seems to "choose" that one assignment works, and another does not, this invites for inadvertent and sometimes hard-to-get mistakes. And frankly, I'm a little worried that in places were type inference can choose between int and float, it will now favor float, where it was int in the past.

The difference between the 2012 and 2015 seems to be that the former takes the first in encounters when going up the hierarchy, and the latter seems to take the last, but I haven't been able to unambiguously confirm that.

Is this a bug or an improvement of an existing feature? I'm afraid I'll have some redesigning to do to remove the ambiguity, unless someone knows of a simple way to deal with this (it happens in only about 50 or so places, doable to fix by hand, but not so nice)?

Preliminary conclusion

It is clear to me that the original type can be considered ambiguous and possibly poor design, but .NET languages support it and so does MSIL.

I know that F# does not support mixing generic types on the same method or property, which is a language choice I can live with, but its type inference makes a decision that cannot be predicted, which I think is a bug.

Either way, I believe this should be an error, similar to the one you get when you address overloaded members in F#, in which case the error is very clear and lists the options.


回答1:


I swapped the order of the interfaces in the C# code to this and the following code compiled as in VS 2013

public interface IInt : IPrime<int>, IFloat
{
   int Salary { get; }
}
let g = fun (itm: Base) -> 
    match itm with
    | :? IInt as i -> i.Value
    | :? IFloat as i -> i.Value |> int
    | _ -> failwith "error"

I suspect that one of the 'Value' members is hidden (shadowed by FSharp's perspective) by the other, based on the order of the interfaces.

I coded your pattern matching like this instead, and validated that the interface order did not matter, in this case.

let g = fun (itm: Base) -> 
    match itm with
    | :? IPrime<int> as i -> i.Value
    | :? IPrime<float> as i -> i.Value |> int
    | _ -> failwith "error"

To me, this seems like a change in the implementation details. I am not sure if I would call it a bug. The F# specification would be the final word if this is a bug.



来源:https://stackoverflow.com/questions/33533647/difference-between-treatment-of-ambiguous-generic-interfaces-by-f-between-vs-20

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!