Home > OS >  Constraining F# function parameter to multiple instantiations of a generic interface fails with FS01
Constraining F# function parameter to multiple instantiations of a generic interface fails with FS01

Time:04-08

I'm trying to constrain an F# function parameter to conform to multiple interfaces, which are in turn derived from the same generic interface declaration:

module GenericInterfaceTest =

  (* declare an interface using generic type *)
  type IReader<'a> =
    abstract member readValue : 'a

  (* constraining only int works *)
  let useReaderInt<'r when 'r :> IReader<int>> (reader : 'r) =
    (reader :> IReader<int>).readValue

  (* constraining only float works *)
  let useReaderFloat<'r when 'r :> IReader<float>> (reader : 'r) =
    (reader :> IReader<float>).readValue

  (* [FS0193] Type constraint mismatch.
     The type ''r' is not compatible with type 'IReader<float>' *)
  let useReaders<'r when 'r :> IReader<int> and 'r :> IReader<float>> (reader : 'r) =
    (reader :> IReader<int>).readValue, (reader :> IReader<float>).readValue

  (* [FS0193] Type constraint mismatch.
     The type ''r' is not compatible with type 'IReader<int>' *)
  let useReaders<'r when 'r :> IReader<float> and 'r :> IReader<int>> (reader : 'r) =
    (reader :> IReader<int>).readValue, (reader :> IReader<float>).readValue


  (* ------------------------------------------------------------------------
     Not using generic type works
     ------------------------------------------------------------------------ *)
  type IIntReader =
    abstract member readValue : int

  type IFloatReader =
    abstract member readValue : float

  (* works *)
  let useReaders<'r when 'r :> IIntReader and 'r :> IFloatReader> (reader : 'r) =
    (reader :> IIntReader).readValue, (reader :> IFloatReader).readValue


  (* ------------------------------------------------------------------------
     Using different generic declarations works
     ------------------------------------------------------------------------ *)
  type IReader2<'a> =
    abstract member readValue : 'a

  (* works *)
  let useReaders<'r when 'r :> IReader<int> and 'r :> IReader2<float>> (reader : 'r) =
    (reader :> IReader<int>).readValue, (reader :> IReader2<float>).readValue


  (* ------------------------------------------------------------------------
     Using type aliases does not work
     ------------------------------------------------------------------------ *)
  type IReaderInt = IReader<int>
  type IReaderFloat = IReader<float>

  (* [FS0193] Type constraint mismatch.
     The type ''r' is not compatible with type 'IReaderInt' *)
  let useReaders<'r when 'r :> IReaderFloat and 'r :> IReaderInt> (reader : 'r) =
    (reader :> IReader<int>).readValue, (reader :> IReader2<float>).readValue

Is this a compiler bug or limitation, or am I doing something wrong?

Is there another way of achieving this without having to write the code multiple times using concrete types?

Edit: I have found the issue on fslang-suggestions: https://github.com/fsharp/fslang-suggestions/issues/1036

It is a limitation involving type inference.

One of the comments mentions working around this by having intermediate non-generic interfaces inheriting generic specialisations. I would be very grateful if someone could help me with an example of how to do this - my best guess below does not work:

module GenericInterfaceTest2 =
  (* declare an interface using generic type *)
  type IReader<'a> =
    abstract member readValue : 'a

  type IReaderInt = inherit IReader<int>

  type IReaderFloat = inherit IReader<float>

  (* works *)
  let useReaders<'r when 'r :> IReaderInt> (reader : 'r) =
    (reader :> IReader<int>).readValue

  (* [FS0193] Type constraint mismatch.
     The type ''r' is not compatible with type 'IReaderFloat' *)
  let useReaders<'r when 'r :> IReaderInt and 'r :> IReaderFloat> (reader : 'r) =
    (reader :> IReaderInt).readValue, (reader :> IReaderFloat).readValue

CodePudding user response:

This isn't pretty, but it compiles:

type IReaderIntFloat =
    inherit IReader<int>
    inherit IReader<float>

let useReaders (reader : IReaderIntFloat) =
    (reader :> IReader<int>).readValue, (reader :> IReader<float>).readValue

CodePudding user response:

You can use non-generic wrapper types and flexible types:

// Input type
type IReader<'a> =
    abstract member readValue : 'a

// Wrapper types
type IReaderInt =
    abstract readInt : int

type IReaderFloat =
    abstract readFloat : float

// Implementation
type NumberReader(intReader: IReader<int>, floatReader: IReader<float>) =
    interface IReaderInt with
        member _.readInt = intReader.readValue

    interface IReaderFloat with
        member _.readFloat = floatReader.readValue

// Helpers
let readInt (reader: #IReaderInt) =
    reader.readInt

let readFloat (reader: #IReaderFloat) =
    reader.readFloat

// Final function
let readNumbers reader =
    readInt reader, readFloat reader
// val readNumbers:
//  reader: 'a -> int * float when 'a :> IReaderInt and 'a :> IReaderFloat

At first, I've tried using inheritance but type inference does not work any more

  • Related