F# - Type Constraints. Any Record with property "id" or get property by string
I have a function in an Entity library - basically you pass a record of any type into it, so long as the type has a property id.
I am using this, which works perfectly somehow:
let inline Update (record: ^x) =
let tableName = GetDatabaseTable<'x> (*E.g if record is of type User, it returns the users table *)
let id = ((^x) : (member id : int) record)
update {
table tableName
set record
where (eq "id" id)
excludeColumn "id"
}
|> db.Update
However when I try to add another function that also takes a generic type argument, that constraint ceases to work:
(*
Pass in a User record of {id=1, name="Username", usergroup_id=2}
and a type argument of <order> and it should return
all orders with a "user_id" value of 1.
*)
let inline GetAllRelated<'x> (record: ^y) =
let id = ((^y) : (member id : int) record) (*Causes an error, type constraint mismatch*)
let tableName = GetDatabaseTable<'x>
let columnName = nameof<'x> + "_id"
select {
table tableName
where (eq columnName id)
} |> db.Select<'x>
Type constraint mismatch when applying the default type 'obj' for a type inference variable. The type 'obj' does not support the operator 'get_id' Consider adding further type constraints
Also constrains ^y to be an obj.
For future functions I will also want to be able to access a record property dynamically, which I'm assuming might be possible with reflection but not sure exactly how. Just with getProperty?
For example a function that is something like
let GetRelated<'x> (record: 'y) =
let columnToGet = nameof<'x> + "_id"
(*
If you do something like
GetRelated<usergroup> {id=1, name="Username", usergroup_id=2}
then that should get the property "usergroup_id" of the passed in record.
*)
Is this stuff possible to do? I know it's a bit janky.
The problem is that your function will actually have more type parameters, so you can specify none of them or all, but if you specify only one it will force the other type params to obj
which is what's happening here.
A possible solution is to write it like this:
let inline GetRelated<'x, ^y when ^y: (member id: int) > (record: 'y) = ...