I have some code which has a lot of repetition.
type RecordA = {
Name: string
// ...
}
type RecordB = {
Name: string
// ...
}
val getTheHandler: (name: string) -> (() -> ())
let handleA (record: RecordA) =
(getTheHandler record.Name) ()
let handleB (record: RecordB) =
(getTheHandler record.Name) ()
I'm wondering if it is possible to write some generic function that would let me simplify/refactor the getTheHandler record.Name
. In trying to refactor that snippet the compiler wants to choose one record type of the other.
So trying this, I get a compiler error:
let shorter (record: 'T) =
(getTheHandler record.Name) ()
// later:
shorter myRecordA // FS0001: This expression was expected to have type RecordB but here has type RecordA
Is this possible? Is the only way to make this work to add a member function to each record type?
Yes, it is possible with SRTP - see here: Partial anonymous record in F#
Here's an example using your use case:
let getTheHandler (name: string) () = printfn $"{name}"
let inline handle (r: ^T) =
(^T : (member Name: string) r)
let ra: RecordA = { Name = "hello" }
let rb: RecordB = { Name = "world" }
handle ra // "hello"
handle rb // "world"
I'd also say though, a little repetition isn't that bad. SRTPs can be wonderful, but can lead down a path of getting way too happy with abstraction and sometimes compile-time slow downs. Using it judiciously like this isn't that bad though.
Just for the record, you can also solve this problem by using ordinary object-oriented interfaces. This is something that works quite well with functional design in F# and it is quite clean. There is some more work involved in explicitly implementing the interfaces, but the up side is that you end up with more clear explicit code (and the interface can model the intention better than just a member name):
To define and implement an interface:
type INamed =
abstract Name : string
type RecordA =
{ Name: string }
interface INamed with
member x.Name = x.Name
type RecordB =
{ Name: string }
interface INamed with
member x.Name = x.Name
To use this:
let getTheHandler (name:string) =
fun () -> printfn "Hi %s" name
let handle (record: INamed) =
(getTheHandler record.Name) ()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With