Why doesn't type implemented in f# behave the same as C# types when called from C# application?

Im in the middle of porting my C# code to a F# library. I have the following interfaces/classes in my C# library:

public interface IMatch<out T> where T : IGame
{
    IEnumerable<T> Games { get; }
}

public interface IGame
{
    string Name { get; }
}

public class SoccerMatch : IMatch<SoccerGame>
{
    public SoccerMatch(IEnumerable<SoccerGame> games)
    {
        Games = games;
    }

    public IEnumerable<SoccerGame> Games { get; }
}

public class SoccerGame : IGame
{
    public SoccerGame(string name)
    {
        Name = name;
    }

    public string Name { get; }
}

I have tried to port this to F#, this is what I've come up with:

type IGame =
    abstract member Name: string with get

type IMatch<'T when 'T :> IGame> =
    abstract member Games: IEnumerable<'T> with get

type SoccerGame =
    {Name: string}
    interface IGame with
        member this.Name with get() = this.Name

type SoccerMatch =
    { Games: IEnumerable<SoccerGame>}
    interface IMatch<SoccerGame> with
        member this.Games: IEnumerable<SoccerGame> = this.Games

The problem is, I need to call this F# library from my C# application. Before, when using the C# classes, I could do the following:

var match= new SoccerMatch(new List<SoccerGame>());
IMatch<IGame> interfaceType = match;

But when I try to do the same with my F# library like this:

var match = new SoccerMatch(new List<SoccerGame>());
IMatch<IGame> interfaceType = match;

I get the following error: Error CS0029 Cannot implicitly convert type 'FSharp.SoccerMatch' to 'FSharp.IMatch'

Im thinking that something must be wrong in my F# implementation(obviously), but what?

1 answer

  • answered 2018-05-21 12:19 Evk

    Your F# type doesn't behave the same as C# one because it's not the same as C# one. C# one has T parameter declared as "out":

    public interface IMatch<out T> where T : IGame
    

    That out means type parameter T is covariant, and that's exactly what allows implicit conversion from SoccerMatch (which is IMatch<SoccerGame>) to IMatch<IGame>.

    However, F# does not support covariance \ contravariance in generic interfaces, as far as I know. It has been suggested by years, but issue is still open. So your F# interface is analog of this C# one:

    public interface IMatch <T> where T : IGame
    {
        IEnumerable<T> Games { get; }
    }
    

    Which will produce the same compile-time error.