Skip to content

Instantly share code, notes, and snippets.

@medigor
Forked from atsapura/Srtp and Records.fs
Created July 24, 2019 05:29
Show Gist options
  • Save medigor/256b9b62759109ec87c88ebd8b5270f2 to your computer and use it in GitHub Desktop.
Save medigor/256b9b62759109ec87c88ebd8b5270f2 to your computer and use it in GitHub Desktop.
(*
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