{"id":32945936,"url":"https://github.com/eiriktsarpalis/TypeShape","last_synced_at":"2025-11-12T20:00:44.429Z","repository":{"id":45954205,"uuid":"63956982","full_name":"eiriktsarpalis/TypeShape","owner":"eiriktsarpalis","description":"Practical generic programming for F#","archived":false,"fork":false,"pushed_at":"2025-02-07T13:44:31.000Z","size":1236,"stargazers_count":315,"open_issues_count":0,"forks_count":34,"subscribers_count":15,"default_branch":"main","last_synced_at":"2025-10-02T04:46:57.110Z","etag":null,"topics":["fsharp","generic-programming"],"latest_commit_sha":null,"homepage":"","language":"F#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eiriktsarpalis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-07-22T13:50:07.000Z","updated_at":"2025-09-11T18:14:34.000Z","dependencies_parsed_at":"2024-01-02T22:38:35.059Z","dependency_job_id":"b54f804a-dc2e-4344-b79e-fc3da8d41e7c","html_url":"https://github.com/eiriktsarpalis/TypeShape","commit_stats":{"total_commits":558,"total_committers":12,"mean_commits":46.5,"dds":0.3691756272401434,"last_synced_commit":"ddc64e8d8f8254a172e320b46794b5740c404780"},"previous_names":[],"tags_count":38,"template":false,"template_full_name":null,"purl":"pkg:github/eiriktsarpalis/TypeShape","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eiriktsarpalis%2FTypeShape","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eiriktsarpalis%2FTypeShape/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eiriktsarpalis%2FTypeShape/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eiriktsarpalis%2FTypeShape/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eiriktsarpalis","download_url":"https://codeload.github.com/eiriktsarpalis/TypeShape/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eiriktsarpalis%2FTypeShape/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":284100246,"owners_count":26947309,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-12T02:00:06.336Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fsharp","generic-programming"],"created_at":"2025-11-12T18:00:25.874Z","updated_at":"2025-11-12T20:00:44.422Z","avatar_url":"https://github.com/eiriktsarpalis.png","language":"F#","readme":"# TypeShape [![Build \u0026 Tests](https://github.com/eiriktsarpalis/TypeShape/workflows/Build%20\u0026%20Tests/badge.svg)](https://github.com/eiriktsarpalis/TypeShape/actions?query=branch%3Amaster) [![NuGet Badge](https://img.shields.io/nuget/dt/TypeShape)](https://www.nuget.org/packages/TypeShape/)\n\nTypeShape is a small, extensible F# library for practical datatype-generic programming.\nBorrowing from ideas used in the FsPickler [implementation](http://mbraceproject.github.io/FsPickler/overview.html#Pickler-Generation),\nit uses a combination of reflection, active patterns and F# object expressions to minimize the\namount of reflection required by the user in such applications.\n\nTypeShape permits definition of programs that act on specific algebrae of types.\nThe library uses reflection to derive the algebraic structure of a given\n`System.Type` instance and then applies a variant of the visitor pattern\nto provide relevant type information per shape.\n\nTypeShape can provide significant performance improvements compared to equivalent reflection-based approaches.\nSee the [performance page](https://github.com/eiriktsarpalis/TypeShape/blob/master/docs/Performance.md) for more details and benchmarks.\n\nPlease see my [article](https://eiriktsarpalis.wordpress.com/2016/08/05/typeshape-practical-generic-programming-in-f/) and [slides](http://eiriktsarpalis.github.io/typeshape/) for a more thorough introduction to the concept.\n\n### Installing\n\nTo incorporate TypeShape in your project place the following line in your\n`paket.dependencies` file:\n```\ngithub eiriktsarpalis/TypeShape:10.0.0 src/TypeShape/TypeShape.fs\n```\nand in `paket.references`:\n```\nFile: TypeShape.fs TypeShape\n```\nTypeShape is also available on [![NuGet Badge](https://buildstats.info/nuget/TypeShape)](https://www.nuget.org/packages/TypeShape/)\n\n### Example: Implementing a value printer\n\n```fsharp\nopen System\nopen TypeShape.Core\n\nlet rec mkPrinter\u003c'T\u003e () : 'T -\u003e string =\n    let wrap(p : 'a -\u003e string) = unbox\u003c'T -\u003e string\u003e p\n    match shapeof\u003c'T\u003e with\n    | Shape.Unit -\u003e wrap(fun () -\u003e \"()\")\n    | Shape.Bool -\u003e wrap(sprintf \"%b\")\n    | Shape.Byte -\u003e wrap(fun (b:byte) -\u003e sprintf \"%duy\" b)\n    | Shape.Int32 -\u003e wrap(sprintf \"%d\")\n    | Shape.Int64 -\u003e wrap(fun (b:int64) -\u003e sprintf \"%dL\" b)\n    | Shape.String -\u003e wrap(sprintf \"\\\"%s\\\"\")\n    | Shape.FSharpOption s -\u003e\n        s.Element.Accept {\n            new ITypeVisitor\u003c'T -\u003e string\u003e with\n                member _.Visit\u003c'a\u003e () =\n                    let tp = mkPrinter\u003c'a\u003e()\n                    wrap(function None -\u003e \"None\" | Some t -\u003e sprintf \"Some (%s)\" (tp t))\n        }\n\n    | Shape.FSharpList s -\u003e\n        s.Element.Accept {\n            new ITypeVisitor\u003c'T -\u003e string\u003e with\n                member _.Visit\u003c'a\u003e () =\n                    let tp = mkPrinter\u003c'a\u003e()\n                    wrap(fun ts -\u003e ts |\u003e List.map tp |\u003e String.concat \"; \" |\u003e sprintf \"[%s]\")\n        }\n\n    | Shape.Array s when s.Rank = 1 -\u003e\n        s.Element.Accept {\n            new ITypeVisitor\u003c'T -\u003e string\u003e with\n                member _.Visit\u003c'a\u003e () =\n                    let tp = mkPrinter\u003c'a\u003e ()\n                    wrap(fun ts -\u003e ts |\u003e Array.map tp |\u003e String.concat \"; \" |\u003e sprintf \"[|%s|]\")\n        }\n        \n    | Shape.Tuple (:? ShapeTuple\u003c'T\u003e as shape) -\u003e\n        let mkElemPrinter (shape : IShapeMember\u003c'T\u003e) =\n           shape.Accept { new IMemberVisitor\u003c'T, 'T -\u003e string\u003e with\n               member _.Visit (shape : ShapeMember\u003c'DeclaringType, 'Field\u003e) =\n                   let fieldPrinter = mkPrinter\u003c'Field\u003e()\n                   fieldPrinter \u003c\u003c shape.Get }\n\n        let elemPrinters : ('T -\u003e string) [] = shape.Elements |\u003e Array.map mkElemPrinter\n\n        fun (r:'T) -\u003e\n            elemPrinters\n            |\u003e Seq.map (fun ep -\u003e ep r)\n            |\u003e String.concat \", \"\n            |\u003e sprintf \"(%s)\"\n\n    | Shape.FSharpSet s -\u003e\n        s.Accept {\n            new IFSharpSetVisitor\u003c'T -\u003e string\u003e with\n                member _.Visit\u003c'a when 'a : comparison\u003e () =\n                    let tp = mkPrinter\u003c'a\u003e()\n                    wrap(fun (s:Set\u003c'a\u003e) -\u003e s |\u003e Seq.map tp |\u003e String.concat \"; \" |\u003e sprintf \"set [%s]\")\n        }\n\n    | _ -\u003e failwithf \"unsupported type '%O'\" typeof\u003c'T\u003e\n\nlet p = mkPrinter\u003cint * bool option * string list * int []\u003e ()\np (42, Some false, [\"string\"], [|1;2;3;4;5|])\n// val it : string = \"(42, Some (false), [\"string\"], [|1; 2; 3; 4; 5|])\"\n```\n\n### Records, Unions and POCOs\n\nTypeShape can be used to define generic programs that access fields of arbitrary types:\nF# records, unions or POCOs. This is achieved using the `IShapeMember` abstraction:\n```fsharp\ntype IShapeMember\u003c'DeclaringType, 'Field\u003e =\n    abstract Get : 'DeclaringType -\u003e 'Field\n    abstract Set : 'DeclaringType -\u003e 'Field -\u003e 'DeclaringType\n```\nAn F# record then is just a list of member shapes, a union is a list of lists of member shapes.\nMember shapes can optionally be configured to generate code at runtime for more performant `Get` and `Set` operations.\nMember shapes come with quoted versions of the API for staged generic programming applications.\n\nTo make our pretty printer support these types, we first provide a pretty printer for members:\n```fsharp\nlet mkMemberPrinter (shape : IShapeMember\u003c'DeclaringType\u003e) =\n   shape.Accept { new IMemberVisitor\u003c'DeclaringType, 'DeclaringType -\u003e string\u003e with\n       member _.Visit (shape : ShapeMember\u003c'DeclaringType, 'Field\u003e) =\n           let fieldPrinter = mkPrinter\u003c'Field\u003e()\n           fieldPrinter \u003c\u003c shape.Get }\n```\nThen for F# records:\n```fsharp\n    match shapeof\u003c'T\u003e with\n    | Shape.FSharpRecord (:? ShapeFSharpRecord\u003c'T\u003e as shape) -\u003e\n        let fieldPrinters : (string * ('T -\u003e string)) [] = \n            s.Fields |\u003e Array.map (fun f -\u003e f.Label, mkMemberPrinter f)\n\n        fun (r:'T) -\u003e\n            fieldPrinters\n            |\u003e Seq.map (fun (label, fp) -\u003e sprintf \"%s = %s\" label (fp r))\n            |\u003e String.concat \"; \"\n            |\u003e sprintf \"{ %s }\"\n```\nSimilarly, we could also add support for arbitrary F# unions:\n```fsharp\n    match shapeof\u003c'T\u003e with\n    | Shape.FSharpUnion (:? ShapeFSharpUnion\u003c'T\u003e as shape) -\u003e\n        let cases : ShapeFSharpUnionCase\u003c'T\u003e [] = shape.UnionCases // all union cases\n        let mkUnionCasePrinter (case : ShapeFSharpUnionCase\u003c'T\u003e) =\n            let fieldPrinters = case.Fields |\u003e Array.map mkMemberPrinter\n            fun (u:'T) -\u003e \n                fieldPrinters \n                |\u003e Seq.map (fun fp -\u003e fp u) \n                |\u003e String.concat \", \"\n                |\u003e sprintf \"%s(%s)\" case.CaseInfo.Name\n\n        let casePrinters = cases |\u003e Array.map mkUnionCasePrinter // generate printers for all union cases\n        fun (u:'T) -\u003e\n            let tag : int = shape.GetTag u // get the underlying tag for the union case\n            casePrinters.[tag] u\n```\nSimilar active patterns exist for classes with settable properties and general POCOs.\n\n### Extensibility\n\nTypeShape can be extended to incorporate new active patterns supporting arbitrary shapes.\nHere's an [example](https://github.com/eiriktsarpalis/TypeShape/blob/5dabaf0577d8387c5213a496099598bbd89650b8/src/TypeShape/ISerializableExtensions.fs) \nillustrating how TypeShape can be extended to support ISerializable shapes.\n\n### Additional examples\n\nSee the project [samples](https://github.com/eiriktsarpalis/TypeShape/tree/master/samples) folder for more implementations using TypeShape:\n\n* [Printer.fs](samples/TypeShape.Samples/printer.fs) Pretty printer generator for common F# types.\n* [Parser.fs](samples/TypeShape.Samples/parser.fs) Parser generator for common F# types using FParsec.\n* [Equality-Comparer.fs](samples/TypeShape.Samples/equality-comparer.fs) Equality comparer generator for common F# types.\n* [hashcode-staged.fs](samples/TypeShape.Samples/hashcode-staged.fs) Staged generic hashcode generator.\n* [Gmap](https://github.com/eiriktsarpalis/TypeShape/blob/master/src/TypeShape/Applications/Combinators.fs#L304) There are set of `gmap` related functions within the `TypeShape.Generic` module in the Nuget package. \n\n### Using the Higher-Kinded Type API\n\nAs of [TypeShape 8](https://github.com/eiriktsarpalis/TypeShape/releases/tag/8.0.0) it is possible to avail of a higher-kinded type flavour of the api,\nwhich can be used to author fully type-safe programs for most common applications.\nPlease see [my original article](https://eiriktsarpalis.wordpress.com/2019/07/02/applying-the-tagless-final-pattern-in-f-generic-programs/) on the subject for background and motivation.\n\nTo use the new approach, we first need to specify which types we would like our generic program to support:\n\n```fsharp\nopen TypeShape.HKT\n\ntype IMyTypesBuilder\u003c'F\u003e =\n    inherit IBoolBuilder\u003c'F\u003e\n    inherit IInt32Builder\u003c'F\u003e\n    inherit IStringBuilder\u003c'F\u003e\n\n    inherit IFSharpOptionBuilder\u003c'F\u003e\n    inherit IFSharpListBuilder\u003c'F\u003e\n    inherit ITuple2Builder\u003c'F\u003e\n```\n\nThe interface `MyTypeBuilder\u003c'F\u003e` denotes a \"higher-kinded\" generic program builder\nwhich supports combinations of boolean, integer, string, optional, list and pair types. \n\nNext, we need to define how interface implementations are to be folded:\n\n```fsharp\nlet mkGenericProgram (builder : IMyTypesBuilder\u003c'F\u003e) =\n    { new IGenericProgram\u003c'F\u003e with\n        member this.Resolve\u003c'a\u003e () : App\u003c'F, 'a\u003e = \n            match shapeof\u003c'a\u003e with\n            | Fold.Bool builder r -\u003e r\n            | Fold.Int32 builder r -\u003e r\n            | Fold.String builder r -\u003e r\n            | Fold.Tuple2 builder this r -\u003e r\n            | Fold.FSharpOption builder this r -\u003e r\n            | Fold.FSharpList builder this r -\u003e r\n            | _ -\u003e failwithf \"I do not know how to fold type %O\" typeof\u003c'a\u003e }\n```\n\nThis piece of boilerplate composes built-in `Fold.*` active patterns,\nwhich contain folding logic for the individual builders inherited by the interface.\nNote that the order of composition can be significant (e.g. folding with `FSharpOption` before `FSharpUnion`).\n\nLet's now provide a pretty-printer implementation for our interface:\n\n```fsharp\n// Higher-Kinded encoding\ntype PrettyPrinter =\n    static member Assign(_ : App\u003cPrettyPrinter, 'a\u003e, _ : 'a -\u003e string) = ()\n\n// Implementing the interface\nlet prettyPrinterBuilder =\n    { new IMyTypesBuilder\u003cPrettyPrinter\u003e with\n        member _.Bool () = HKT.pack (function false -\u003e \"false\" | true -\u003e \"true\")\n        member _.Int32 () = HKT.pack (sprintf \"%d\")\n        member _.String () = HKT.pack (sprintf \"\\\"%s\\\"\")\n\n        member _.Option (HKT.Unpack elemPrinter) = HKT.pack(function None -\u003e \"None\" | Some a -\u003e sprintf \"Some(%s)\" (elemPrinter a))\n        member _.Tuple2 (HKT.Unpack left) (HKT.Unpack right) = HKT.pack(fun (a,b) -\u003e sprintf \"(%s, %s)\" (left a) (right b))\n        member _.List (HKT.Unpack elemPrinter) = HKT.pack(Seq.map elemPrinter \u003e\u003e String.concat \"; \" \u003e\u003e sprintf \"[%s]\") }\n```\n\nPutting it all together gives us a working pretty-printer:\n\n```fsharp\nlet prettyPrint\u003c't\u003e : 't -\u003e string = (mkGenericProgram prettyPrinterBuilder).Resolve\u003c't\u003e () |\u003e HKT.unpack\n\nprettyPrint 42\nprettyPrint (Some false)\nprettyPrint (Some \"test\", [Some 42; None; Some -1])\n```\n\nPlease check the [samples/HKT](https://github.com/eiriktsarpalis/TypeShape/tree/main/samples/TypeShape.Samples/HKT) folder for real-world examples of the above.\n\n### Projects using TypeShape\n\n* [FsPickler](https://github.com/mbraceproject/FsPickler/blob/c5b73fc48fa313c66eaeb8c79897253de0605d34/src/FsPickler/PicklerGeneration/PicklerGenerator.fs#L38)\n* [FSharp.AWS.DynamoDB](https://github.com/fsprojects/FSharp.AWS.DynamoDB/blob/55470a0cc8b1a54d14571f059bd2b5721f2495c7/src/FSharp.AWS.DynamoDB/Picklers/PicklerResolver.fs#L23)\n* [Logary](https://github.com/logary/logary)\n* [Equinox/FsCodec](https://github.com/jet/FsCodec)\n* [FsConfig](https://github.com/demystifyfp/FsConfig)\n\n### Related Work\n\n* [DataType Generic Programming in F#](http://www.staff.science.uu.nl/~swier004/publications/2015-wgp.pdf), \n* [Infers](https://github.com/Infers/Infers)\n","funding_links":[],"categories":["Misc","杂项","General Purpose Libraries","Audio"],"sub_categories":["GUI - other","Performance Analysis"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feiriktsarpalis%2FTypeShape","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feiriktsarpalis%2FTypeShape","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feiriktsarpalis%2FTypeShape/lists"}