(* | |
WHAT'S GOING ON HERE?! | |
Sometimes you don't care about a particular type, you're interested in one field only, let's say `EntityId`. | |
Instead of using interface (which isn't even possible if don't own a type), | |
we can do structural typing in F# using SRTP and Active Patterns. | |
Active patterns are not required for this, but they do make code much easier to use. | |
*) | |
// So we have 2 types with field `EntityId: string`: | |
type Test = | |
{ EntityId: string } | |
type Test2 = | |
{ EntityId: string | |
Name: string | |
Length: int } | |
// First we define active pattern for detecting | |
// whether this type has this field or not: | |
let inline (|HasEntityId|) x = | |
fun () -> (^a : (member EntityId: string) x) // as you can see, SRTP syntax is hardly comfortable to use | |
// Then we define function for retrieving this field | |
let inline entityId (HasEntityId f) = f() | |
// Another AP, this time for detecting `Name: string`: | |
let inline (|HasName|) n = | |
fun () -> (^a : (member Name: string) n) | |
// and function for getting the name: | |
let inline name (HasName f) = f() | |
// Now here's the beauty of it: we can combine them! | |
let inline printNameAndId (HasName n & HasEntityId id) = sprintf "name %s id %s" (n()) (id()) | |
// Watching it in action: | |
let test1 = { EntityId = "123" } | |
let test2 = {Test2.EntityId = "123"; Name = "123"; Length = 2} | |
// Works both with Test and Test2 types since both have `EntityId` field | |
let a = entityId test1 | |
let b = entityId test2 | |
let c = name test2 | |
let d = printNameAndId test2 | |
// *Compile* error when using type without required fields: | |
let error = printNameAndId test1 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
How can I write like this? I want both generic and duck typing in the same function.