Constraining F# function parameter to multiple instantiations of a generic interface fails with FS0193

Viewed 60

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
3 Answers

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

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

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

// Wrapper types
type IReaderInt =
    inherit IReader<int>

type IReaderFloat =
    inherit IReader<float>

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

    interface IReaderFloat with
        member _.readValue = floatReader.readValue

let readInt (reader: #IReaderInt) =
    reader.readValue

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

let readNumbers reader =
    readInt reader, readFloat reader
//                            ~~~~~~ Error FS0001...

Thank you Romain, you put me on track to the following:

(* Input type *)
type IReader<'a> =
    abstract member readValueTupled : unit -> 'a * 'a
    abstract member readValue : unit -> 'a

(* generic implementation *)
type GenericImplementation<'a> (a : 'a) =
    interface IReader<'a> with
      member this.readValueTupled () = (a, a)
      member this.readValue () = a

module Int =
  (* Wrapper types *)
  type IReader =
      abstract member readValueTupled : unit -> int * int
      abstract member readValue : unit -> int

module Float =
  (* Wrapper types *)
  type IReader =
      abstract member readValue : unit -> float
      abstract member readValueTupled : unit -> float * float

(* Implementation of class, using the generic implementation above *)
type NumberReader(anInt : int, aFloat : float) =
  let IReaderInt = (GenericImplementation(anInt) :> IReader<int>)
  let IReaderFloat = (GenericImplementation(aFloat) :> IReader<float>)

  interface Int.IReader with
    member _.readValueTupled () = IReaderInt.readValueTupled ()
    member _.readValue () = IReaderInt.readValue ()

  interface Float.IReader with
    member _.readValueTupled () = IReaderFloat.readValueTupled ()
    member _.readValue () = IReaderInt.readValue ()

(* Final function *)
(* val reader: 'a (requires 'a :> Int.IReader and 'a :> Float.IReader) *)
let readNumbers reader =
    (reader :> Int.IReader).readValueTupled ()
    , (reader :> Float.IReader).readValue ()

This would allow me to define an interface and write the implementation generically, and then pass different instantiations into functions respecting interface segregation.

I added the second member to test the refactoring, and found it painful to make the correct changes in the right places.

So I've made peace with rather passing in a tuple of required readers at the callsite, which means that I can just do something like this:

  (* Input type *)
  type IReader<'a> =
      abstract member readValueTupled : unit -> 'a * 'a
      abstract member readValue : unit -> 'a
  
  (* generic implementation - do something *)
  type Reader<'a> (a : 'a) =
      interface IReader<'a> with
        member this.readValueTupled () = (a, a)
        member this.readValue () = a
  
  (* Implementation of class, using the generic implementation above *)
  type NumberReaderAndOtherThings(anInt : int, aFloat : float) =
    member _.IReaderInt = Reader<int>(anInt) :> IReader<int>
    member _.IReaderFloat = Reader<float>(aFloat) :> IReader<float>    
  
  
  (* Called function *)
  let useReaders (readerInt : IReader<int>, readerFloat : IReader<float>) =
      readerInt.readValueTupled (), readerFloat.readValueTupled ()

  (* Call site *)
  let useAllThis = 
    let myClass = NumberReaderAndOtherThings(1, 1.1)
    useReaders (myClass.IReaderInt, myClass.IReaderFloat)

In my case there is currently only the generic implementation, so I have realised I can dispense with the interface altogether (for now):

(* generic implementation - do something *)
type Reader<'a> (a : 'a) =
  member this.readValueTupled () = (a, a)
  member this.readValue () = a

(* Implementation of class, using the generic implementation above *)
type NumberReaderAndOtherThings(anInt : int, aFloat : float) =
  member _.IReaderInt = Reader<int>(anInt)
  member _.IReaderFloat = Reader<float>(aFloat)

(* Called function *)
let useReaders (readerInt : Reader<int>, readerFloat : Reader<float>) =
    readerInt.readValueTupled (), readerFloat.readValueTupled ()

(* Call site *)
let useAllThis =
  let myClass = NumberReaderAndOtherThings(1, 1.1)
  useReaders (myClass.IReaderInt, myClass.IReaderFloat)

Thank you everyone for the help, sorry for the long post, maybe it helps someone in the future (all advice and comments welcome).

Related