Is it possible to write a genericFunction<’T> where ‘T is constrained to be a tuple of type 'T2*'T3?

Viewed 118

Is it possible in F# to write a generic function where you specify a type 'T, but constrain 'T to be a tuple of type 'T2*'T3?

Here's a couple of things I've tried that just produces compiler errors:

let fun1<'T when 'T :> 'T2*'T3> () = …
let fun2<'T, 'T2, 'T3 when 'T :> 'T2*'T3> () = …

(In fun2 'T2/'T3 would ideally be inferred by 'T1.)

The reason I want to do this is that I'm writing an application with an in-memory database, and I'm trying to write a generic function which extracts data associated with a database-entity in the specified tuple-form. I have a solution that works, but has a few drawbacks:

let workingExtractEntity<'T> (id : EntityID) (db: Database) : EntityID*'T =
        let t = db.GetEntityComponent id typeof<'T>
        (id, t)

As well as similar functions with more type arguments:

let workingExtractEntity2<'T1, 'T2> (id : EntityID) (db: Database) : EntityID*'T1*'T2 = …

The main problem with this approach is that you have to manually type in and order the generic arguments every time you use the function. I would have preferred being able to specify a single tuple type instead, in combination with type aliases for commonly used tuple types, like so:

//Obviously won’t compile, but illustrates what I would like to do.
let extractEntity2<'T when 'T :> EntityID*'T2*'T3> id db : 'T =
        let t2 = db.GetEntityComponent id typeof<'T2>
        let t3 = db.GetEntityComponent id typeof<'T3>
        (id, t2, t3)

type Player = EntityID*Position*Health
    
let extractPlayer id db : Player = extractEntity2<Player> id db

Note that no objects of type Player are stored in the database, but rather only the components that make up a Player, namely Position and Health, are stored there. (Any entity with a Position and Health associated with it could be represented as a Player.)

Is there any way to achieve something like this?

1 Answers

I’ve done some research and I think I have found the answer to my own question:

Unfortunately what I want to do seems not to be possible in F#, and reason is nicely summarized here by the user Gus:

I'm afraid there is no way to add a subtype constraint to a generic type parameter based on another one in F#. They're always assumed to be equal, see the spec New constraints of the form type :> 'b are solved again as type = 'b.

This limitation doesn’t exist in C#, which makes interop troublesome sometimes. There is an issue on the F# language suggestion github page addressing it.

In other words you can’t write a function like this:

let foo<'T, 'U when 'T :> 'U> () = …

And it means you can’t do this either:

let foo2<'T, 'T2, 'T3 when 'T :> Tuple<'T2, 'T3>> () = …

Which is essentially what I wanted, except with additional type inference, so that instead of typing out the whole:

let callFoo2 = foo2<int*string, int, string> ()

You could just write it like this, and have 'T2 and 'T3 be inferred from 'T1:

let callFoo2Differently = foo2<int*string> ()
Related