cameron
cameron

Reputation: 91

Creating and using a generic function in a non generic F# interface

It is beyond me at this point. I'm trying to create an interface that looks something like this.

type IFetchData = 
     abstract FetchData: string -> seq<'a>

The above declaration is valid (and compiles) but when I go to use it I get a compile time error. This expression was expected to have type 'a but here has type "what I'm currently trying to return" i.e. seq.

My example usage however looks like the following:

type SampleFetchData() =
    interface IFetchData with
        member self.FetchData str =           
           seq {
                    for letter in str do
                        yield letter // compile error here
                }

I'm not sure what I'm doing wrong. All I'd like to do is allow the interface implementer to be able to write any function that returns a generic sequence either seq<string>,seq<int>,seq<record type here>, seq<union type here>, etc.

Can someone tell me what I'm missing here?

Thanks.

Upvotes: 3

Views: 526

Answers (2)

Tomas Petricek
Tomas Petricek

Reputation: 243126

If you're loading the interface implementation using Reflection, then it is going to be quite difficult to work with it. The problem is that you get an object of type obj. You know that it implements IFetchData<'T> for some 'T, but statically, you don't know for which 'T. This is a problem because you can't cast the object to any more specific type - if you tried using IFetchData<obj>, it wouldn't work because you can't cast, for example, IFetchData<int> to that type.

I would recommend using a non-generic interface, which is quite common .NET pattern:

type IFetchDataUntyped = 
  abstract FetchData : string -> System.Collections.IEnumerable

type IFetchData<'T> =  
  inherit IFetchDataUntyped
  abstract FetchData : string -> seq<'T> 

When you load an implementation using Reflection, you can cast the object to IFetchDataUntyped and work with it in a fairly reasonable way (using Seq.cast to convert the sequence to a more specific type if you know the element type).

Depending on your application, you may also just make the FetchData method a generic method and keep the interface non-generic. Then you could cast dynamically loaded objects to the interface and invoke the method. However, this changes the design (because the method has to work for any type it gets as a type parameter):

type IFetchData =  
  abstract FetchData<'T> : string -> seq<'T>  // Note: Generic parameter here!

Upvotes: 4

John Palmer
John Palmer

Reputation: 25526

You need to do something like

type IFetchData<'a> = 
     abstract FetchData: string -> seq<'a>

type SampleFetchData() =
    interface IFetchData<char> with
        member self.FetchData str =           
           seq {
                    for letter in str do
                        yield letter 
                }

i.e. the interface needs to be made generic. If you want to avoid the genericness you could use some inline constraints, rather than an interface

EDIT: Inline magic version

let inline Fetchdata string obj=
   (^a: (member FetchData: string -> seq<'b> )(obj, string))

type SampleFetchData() =
        member self.FetchData str =           
           seq {
                    for letter in str do
                        yield letter 
                }

Fetchdata "hello" (new SampleFetchData())

Upvotes: 3

Related Questions