Generic function definitions in F# signature files and corresponding implementation file

559 Views Asked by At

I'm trying to create an abstraction for a lightweight data storage module using an F# signature file. Here is my signature file code, let's say it's called repository.fsi

namespace DataStorage

/// <summary>Lightweight Repository Abstraction</summary>
module Repository = 
   /// <summary> Insert data into the repository </summary>
   val put: 'a -> unit
   /// <summary> Fetch data from the repository </summary>
   val fetch: 'a -> 'b
   /// <summary> Remove data from the repository </summary>
   val remove: 'a -> unit

Here is the corresponding implementation, let's call it repository.fs

namespace DataStorage

module Repository = 

    (* Put a document into a database collection *)
    let put entity = ()

    (* Select a document from a database collection *)
    let fetch key = ("key",5)

    (* Remove a document from a database collection *)
    let remove entity = ()

In my visual studio project file I have the signature file (repository.fsi) above my implementation file (repository.fs). The put and remove functions are being parsed and verified correctly with no errors (in the implementation file) but the fetch function keeps giving me the red squiggly in visual studio with the following error message:

Module 'DataStorage.Repository' contains

val fetch: s:string -> string * int

but its signature specifies

val fetch<'a,'b> : 'a -> 'b

The respective type parameter counts differ

Can someone tell me what I'm doing wrong? Is my fetch function value defined wrong in my signature file? I'm just trying to create a generic function ('a -> 'b) in my signature file and have the implementation take one type as input and return a different type as output.

2

There are 2 best solutions below

1
On

I am not very strong in F# yet but I think here you are using signature files in wrong way.

First. Here is how you can fix compilation errors:

Replace:

let fetch key = ("key",5)

with:

let fetch key = ("key",5) :> obj :?> 'b

and you won't get compilation errors. But this fix doesn't actually makes sense in many cases. For example if next works:

let a = Repository.fetch(56)

if you specify type explicity it will crash (in most cases):

let b = Repository.fetch<int, string>(56)

The case is that generic implementation should operate with generic types. If I understod correctly from what are you trying to do you should use OOP and polymophism when signature files are used to hide implementation aspects. For example:

namespace DataStorage

[<AbstractClass>]
type Repository<'TKey,'T>() =     
    abstract member put: 'T -> unit
    abstract member fetch: 'TKey -> 'T  
    abstract member remove: 'T -> unit

type IntRepository() =
    inherit Repository<int, int>()
    override self.put item = ()
    override self.fetch index = 5
    override self.remove item = ()

type StringRepository() =
    inherit Repository<int, string>()
    override self.put item = ()
    override self.fetch index = "Hello"
    override self.remove item = ()
3
On

One (some what limiting) alternative that I've recently tried is sort of one step away from generics but seems to work for my scenario. Basically the signature file for the fetch function now looks like this.

'a -> RepositoryRecord

and the implementation of the RepositoryRecord is an algebraic datatype.

type RepositoryRecord = | SomeVal1 of int * string | SomeVal2 of string