Skip to content

Instantly share code, notes, and snippets.

@atsapura
Last active August 2, 2023 16:51
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save atsapura/fd9d7aa26e337eaa2f7f04d6cbb58ef6 to your computer and use it in GitHub Desktop.
Save atsapura/fd9d7aa26e337eaa2f7f04d6cbb58ef6 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
@7c78
Copy link

7c78 commented Aug 1, 2019

How can I write like this? I want both generic and duck typing in the same function.

module T =
    let inline (|Id|) x =
        fun () -> (^a : (member Id: string) x)

    let inline id (Id f) = f()

    let inline test<'a> param =
        let aT = typeof<'a>.Name
        let idStr = id param
        sprintf "%s %s" aT idStr

    type C =
        { Name: string }
        with
        member __.Id = "1"

    let c = { Name = "abc" }

    let b = test<int> c

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment