Skip to content

Instantly share code, notes, and snippets.

@michaeloyer
Last active July 1, 2024 03:15
Show Gist options
  • Select an option

  • Save michaeloyer/615543e33713033dc1c4e01c98dd9f2a to your computer and use it in GitHub Desktop.

Select an option

Save michaeloyer/615543e33713033dc1c4e01c98dd9f2a to your computer and use it in GitHub Desktop.

Revisions

  1. michaeloyer revised this gist May 25, 2023. 3 changed files with 37 additions and 3 deletions.
    24 changes: 23 additions & 1 deletion srtp.fsx
    Original file line number Diff line number Diff line change
    @@ -104,4 +104,26 @@ let inline printPersonCombinedActivePattern (Person (first, last)) =
    printfn $"Person is: {first} {last}"

    printPersonCombinedActivePattern personRecord
    printPersonCombinedActivePattern personClass
    printPersonCombinedActivePattern personClass

    //F# 7 No longer requires ^ character in the definition, the compiler figures it out with the `inline` keyword and member constraints
    let inline printPersonGeneric<'Person
    when 'Person:(member First:string)
    and 'Person:(member Last:string)
    > (person:'Person) =
    printfn $"Person is: {person.First} {person.Last}"

    printPersonGeneric personRecord
    printPersonGeneric personClass

    // F# 7 Grouped Member Constraint
    type PersonMembers<'T
    when 'T:(member First:string)
    and 'T:(member Last:string)> = 'T
    // Here the PersonMembers acts as a wrapper so that we can more easily define generic 'Person type,
    // with all of those members necessary for a "Person" type
    let inline printPersonGenericGroup<'Person when PersonMembers<'Person>> (person:'Person) =
    printfn $"Person is: {person.First} {person.Last}"

    printPersonGenericGroup personRecord
    printPersonGenericGroup personClass
    6 changes: 5 additions & 1 deletion srtp.fsx printed console output.txt
    Original file line number Diff line number Diff line change
    @@ -3,4 +3,8 @@ Person is: Person Class
    Person is: Person Record
    Person is: Person Class
    Person is: Person Record
    Person is: Person Class
    Person is: Person Class
    Person is: Person Record
    Person is: Person Class
    Person is: Person Record
    Person is: Person Class
    10 changes: 9 additions & 1 deletion srtp.fsx type definitions.txt
    Original file line number Diff line number Diff line change
    @@ -41,4 +41,12 @@ val inline (|Person|) :
    val inline printPersonCombinedActivePattern:
    ^a -> unit
    when ^a: (member get_Last: ^a -> string) and
    ^a: (member get_First: ^a -> string)
    ^a: (member get_First: ^a -> string)

    val inline printPersonGeneric:
    person: 'Person (requires member First and member Last )
    -> unit

    val inline printPersonGenericGroup:
    person: 'Person (requires member First and member Last )
    -> unit
  2. michaeloyer revised this gist Feb 2, 2022. 2 changed files with 0 additions and 0 deletions.
    File renamed without changes.
  3. michaeloyer revised this gist Feb 2, 2022. 3 changed files with 51 additions and 61 deletions.
    6 changes: 6 additions & 0 deletions srtp printed console output.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,6 @@
    Person is: Person Record
    Person is: Person Class
    Person is: Person Record
    Person is: Person Class
    Person is: Person Record
    Person is: Person Class
    44 changes: 44 additions & 0 deletions srtp type definitions.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,44 @@
    type PersonRecord =
    {
    First: string
    Last: string
    }

    type PersonClass =
    new: first: string * middle: string * last: string -> PersonClass
    member First: string
    member Last: string
    member Middle: string

    val personRecord: PersonRecord = { First = "Person"
    Last = "Record" }

    val personClass: PersonClass

    val inline printPersonRegularParameter:
    person: ^Person -> unit
    when ^Person: (member get_First: ^Person -> string) and
    ^Person: (member get_Last: ^Person -> string)

    val inline (|FirstName|) :
    o: ^FirstName -> string
    when ^FirstName: (member get_First: ^FirstName -> string)

    val inline (|LastName|) :
    o: ^LastName -> string
    when ^LastName: (member get_Last: ^LastName -> string)

    val inline printPersonActivePatterns:
    ^a -> unit
    when ^a: (member get_Last: ^a -> string) and
    ^a: (member get_First: ^a -> string)

    val inline (|Person|) :
    ^a -> string * string
    when ^a: (member get_Last: ^a -> string) and
    ^a: (member get_First: ^a -> string)

    val inline printPersonCombinedActivePattern:
    ^a -> unit
    when ^a: (member get_Last: ^a -> string) and
    ^a: (member get_First: ^a -> string)
    62 changes: 1 addition & 61 deletions srtp.fsx
    Original file line number Diff line number Diff line change
    @@ -104,64 +104,4 @@ let inline printPersonCombinedActivePattern (Person (first, last)) =
    printfn $"Person is: {first} {last}"

    printPersonCombinedActivePattern personRecord
    printPersonCombinedActivePattern personClass


    // The Console Output from the code above:
    (*
    Person is: Person Record
    Person is: Person Class
    Person is: Person Record
    Person is: Person Class
    Person is: Person Record
    Person is: Person Class
    *)

    // Definitions of the types above:

    (*
    type PersonRecord =
    {
    First: string
    Last: string
    }
    type PersonClass =
    new: first: string * middle: string * last: string -> PersonClass
    member First: string
    member Last: string
    member Middle: string
    val personRecord: PersonRecord = { First = "Person"
    Last = "Record" }
    val personClass: PersonClass
    val inline printPersonRegularParameter:
    person: ^Person -> unit
    when ^Person: (member get_First: ^Person -> string) and
    ^Person: (member get_Last: ^Person -> string)
    val inline (|FirstName|) :
    o: ^FirstName -> string
    when ^FirstName: (member get_First: ^FirstName -> string)
    val inline (|LastName|) :
    o: ^LastName -> string
    when ^LastName: (member get_Last: ^LastName -> string)
    val inline printPersonActivePatterns:
    ^a -> unit
    when ^a: (member get_Last: ^a -> string) and
    ^a: (member get_First: ^a -> string)
    val inline (|Person|) :
    ^a -> string * string
    when ^a: (member get_Last: ^a -> string) and
    ^a: (member get_First: ^a -> string)
    val inline printPersonCombinedActivePattern:
    ^a -> unit
    when ^a: (member get_Last: ^a -> string) and
    ^a: (member get_First: ^a -> string)
    *)
    printPersonCombinedActivePattern personClass
  4. michaeloyer revised this gist Feb 2, 2022. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions srtp.fsx
    Original file line number Diff line number Diff line change
    @@ -23,7 +23,7 @@ type PersonRecord = {
    Last: string
    }

    // Even if another type has extra parameters such as the 'Middle" member in this case
    // Even if the other type has extra members such as the 'Middle" member in this case
    type PersonClass(first: string, middle: string, last: string) =
    member val First = first
    member val Middle = middle
    @@ -43,7 +43,6 @@ let personClass = PersonClass("Person", "Of", "Class")
    // Notice that in the (+) signature the 'static member' was required, but our ^Person type
    // Just needs a regular 'member'

    // Because our 'PersonRecord' and 'PersonClass' are both going to be used in this function
    let inline printPersonRegularParameter person =
    // Notice the value is being pulled out of person with pattern matching
    let first = (^Person : (member First: string) person)
    @@ -53,6 +52,7 @@ let inline printPersonRegularParameter person =
    printPersonRegularParameter personRecord
    printPersonRegularParameter personClass

    // Because our 'PersonRecord' and 'PersonClass' are both going to be used in printPersonRegularParameter
    // We also need to apply the 'inline' keyword to the function
    // This will create two versions of our function in the compiled code,
    // one that takes the 'PersonRecord', and one that takes the 'PersonClass'.
  5. michaeloyer created this gist Feb 2, 2022.
    167 changes: 167 additions & 0 deletions srtp.fsx
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,167 @@
    // SRTP: Statically Resolved Type Parameters
    // https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/generics/statically-resolved-type-parameters

    // SRTP Allows for pulling members out of types that where the member is named and typed the same
    // In this example SRTP will be used to pull out the 'First: string' and 'Last: string' members
    // from different types

    // One example of SRTP in the F# Base Class Library is the (+) operator.
    // You'll see that it has this type signature:

    (*
    val inline ( + ):
    x: ^T1 (requires static member ( + ) ) ->
    y: ^T2 (requires static member ( + ) )
    -> ^T3
    *)

    // Notice the " ^ " symbol to define the generic class instead of " ' "

    // Members can come from either record or class definitions
    type PersonRecord = {
    First: string
    Last: string
    }

    // Even if another type has extra parameters such as the 'Middle" member in this case
    type PersonClass(first: string, middle: string, last: string) =
    member val First = first
    member val Middle = middle
    member val Last = last

    let personRecord = { First = "Person"; Last = "Record"}
    let personClass = PersonClass("Person", "Of", "Class")

    // The person parameter has the type ^Person.
    // The definition of printPersonRegularParameter looks like this:
    (*
    val inline printPersonRegularParameter:
    person: ^Person (requires member First and member Last )
    -> unit
    *)

    // Notice that in the (+) signature the 'static member' was required, but our ^Person type
    // Just needs a regular 'member'

    // Because our 'PersonRecord' and 'PersonClass' are both going to be used in this function
    let inline printPersonRegularParameter person =
    // Notice the value is being pulled out of person with pattern matching
    let first = (^Person : (member First: string) person)
    let last = (^Person : (member Last: string) person)
    printfn $"Person is: {first} {last}"

    printPersonRegularParameter personRecord
    printPersonRegularParameter personClass

    // We also need to apply the 'inline' keyword to the function
    // This will create two versions of our function in the compiled code,
    // one that takes the 'PersonRecord', and one that takes the 'PersonClass'.
    // Without 'inline' this would create one function that takes in either the the
    // 'PersonRecord' or 'PersonClass' type (which ever is used in the function first),
    // and won't compile when we try to use the other type
    (*
    ex.
    let printPersonRegularParameter person = ... (srtp definition from above, just without inline in the signature) ...
    printPersonRegularParameter personRecord // Compiler warning:
    // This construct causes code to be less generic
    // than indicated by the type annotations. The type variable
    // 'Person has been constrained to be type 'PersonRecord'.
    printPersonRegularParameter personClass // Compiler error:
    // This expression was expected to have type
    // 'PersonRecord'
    // but here has type
    // 'PersonClass'
    *)

    // Because pulling values is done with pattern matching we can create
    // an active pattern to tidy up the pattern matching
    let inline (|FirstName|) o =
    // ^FirstName could just as easily be ^T or ^a as long as it's using the '^' symbol
    (^FirstName: (member First: string) o)

    let inline (|LastName|) o =
    (^LastName: (member Last: string) o)

    (* Active Patterns can be composed together with the '&' symbol
    This is still looking for a single parameter defined as
    having the First:string and Last:string members. *)

    // Same signature as printPersonRegularParameter
    let inline printPersonActivePatterns (FirstName first & LastName last) =
    printfn $"Person is: {first} {last}"

    printPersonActivePatterns personRecord
    printPersonActivePatterns personClass

    // And we could define another active pattern that combines our previous active patterns
    // We'll just put return first and last in a tuple
    let inline (|Person|) (FirstName first & LastName last) = first, last

    // Same signature as printPersonRegularParameter and printPersonActivePatterns
    // We can pull the tuple apart with further pattern matching
    let inline printPersonCombinedActivePattern (Person (first, last)) =
    printfn $"Person is: {first} {last}"

    printPersonCombinedActivePattern personRecord
    printPersonCombinedActivePattern personClass


    // The Console Output from the code above:
    (*
    Person is: Person Record
    Person is: Person Class
    Person is: Person Record
    Person is: Person Class
    Person is: Person Record
    Person is: Person Class
    *)

    // Definitions of the types above:

    (*
    type PersonRecord =
    {
    First: string
    Last: string
    }
    type PersonClass =
    new: first: string * middle: string * last: string -> PersonClass
    member First: string
    member Last: string
    member Middle: string
    val personRecord: PersonRecord = { First = "Person"
    Last = "Record" }
    val personClass: PersonClass
    val inline printPersonRegularParameter:
    person: ^Person -> unit
    when ^Person: (member get_First: ^Person -> string) and
    ^Person: (member get_Last: ^Person -> string)
    val inline (|FirstName|) :
    o: ^FirstName -> string
    when ^FirstName: (member get_First: ^FirstName -> string)
    val inline (|LastName|) :
    o: ^LastName -> string
    when ^LastName: (member get_Last: ^LastName -> string)
    val inline printPersonActivePatterns:
    ^a -> unit
    when ^a: (member get_Last: ^a -> string) and
    ^a: (member get_First: ^a -> string)
    val inline (|Person|) :
    ^a -> string * string
    when ^a: (member get_Last: ^a -> string) and
    ^a: (member get_First: ^a -> string)
    val inline printPersonCombinedActivePattern:
    ^a -> unit
    when ^a: (member get_Last: ^a -> string) and
    ^a: (member get_First: ^a -> string)
    *)