简体   繁体   中英

Collection of objects that implement a generic interface in F#

Suppose I have the following code:

type A =
  abstract member hi: string

type B() =
  interface A with
    member self.hi: string = "Hello"

type C() =
  interface A with
    member self.hi: string = "Yo"

I can make F#'s type checker happy with a list of objects of type A and B as long as I explicitly specify the interface type, ala:

let l: A list = [ B(); C() ]

But I'm kind of stumped when generic parameters enter the picture. Eg,

type A<'T> =
  abstract member thing: 'T

type B() =
  interface A<int> with
    member self.thing: int = 1

type C() =
  interface A<string> with
    member self.thing: string = "Yo"

and I try to use something like

let l: A<_> list = [B(); C()]

F# seems to want to stubbornly fill in the generic type parameter:

error FS0001: The type 'C' is not compatible with the type 'A<int>'

Note that I have used this pattern in Java with standard interfaces and in Scala with traits, so I am surprised that I am unable to do this in F#. What am I missing here?

Using _ in type parameter position basically tells the compiler "infer the type for me". The first fully defined type in the list is A<int> so _ is fixed to int . You need to supply a (least common) super-type of all list elements yourself. As F# does not support interface covariance in generics, all you can do here is obj : let l: obj list = [B(); C()] let l: obj list = [B(); C()]

Note that this is true for C# as well, as variance only comes into play for reference types:

interface IInvariant<T>
{
    T Item { get; }
}

interface ICovariant<out T>
{
    T Item { get; }
}

class Foo : IInvariant<int>, ICovariant<int>
{
    public int Item { get; }
}

class Bar : IInvariant<string>, ICovariant<string>
{
    public string Item { get; }
}

class Baz
{
    static void Check()
    {
        var a = new IInvariant<object>[] { new Foo(), new Bar() };
        // CS0266  Cannot implicitly convert type 'Foo' to 'C.IInvariant<object>'
        // CS0266  Cannot implicitly convert type 'Bar' to 'C.IInvariant<object>'

        var b = new ICovariant<object>[] { new Foo(), new Bar() };
        // CS0266  Cannot implicitly convert type 'Foo' to 'C.ICovariant<object>'
    }
}

In F# you could create a discriminated union to capture the type information:

type InvariantWrapper =
| Integer of IInvariant<int>
| Stringy of IInvariant<string>

let c = [ Integer(Foo()); Stringy(Bar()) ]

F# doesn't directly support heterogeneous use of type parameters like that: the _ means some specific unspecified type , not any number of unspecified types . If you desperately want to do something like that, there is a mechanical but awkward encoding of existential types in the .NET type system, but I'd recommend against it unless there's some especially compelling benefit.

Also note that even if you could do what you wanted, you couldn't do much with the list - what operation could you perform on any given element of it?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM