{"id":19596849,"url":"https://github.com/cdiggins/type-inference","last_synced_at":"2025-04-27T15:35:25.413Z","repository":{"id":54820311,"uuid":"109905181","full_name":"cdiggins/type-inference","owner":"cdiggins","description":"Type Inference Library written in TypeScript","archived":false,"fork":false,"pushed_at":"2023-05-07T15:36:26.000Z","size":3629,"stargazers_count":93,"open_issues_count":2,"forks_count":5,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-20T04:17:57.211Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/cdiggins.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-11-07T23:57:58.000Z","updated_at":"2025-01-12T23:09:59.000Z","dependencies_parsed_at":"2022-08-14T03:50:24.656Z","dependency_job_id":null,"html_url":"https://github.com/cdiggins/type-inference","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cdiggins%2Ftype-inference","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cdiggins%2Ftype-inference/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cdiggins%2Ftype-inference/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cdiggins%2Ftype-inference/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cdiggins","download_url":"https://codeload.github.com/cdiggins/type-inference/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251163090,"owners_count":21545873,"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","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":[],"created_at":"2024-11-11T08:56:56.335Z","updated_at":"2025-04-27T15:35:20.883Z","avatar_url":"https://github.com/cdiggins.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Type Inference for Polymorphic Types\n\nThis is a TypeScript implementation of a type-inference algorithm for polymorphic types. \nIt is used in the:\n* [Heron programming language](https://github.com/cdiggins/heron-language)\n* [Cat programming language](https://github.com/cdiggins/cat-language)\n\nThe type inference algorithm is not proven to support rank-N polymorphism in all cases (this is an open question), \nbut it does infer polymorphic types in some cases where other languages using HM inference are known to fail. \n\n# Citation\n\nTo formally cite this project please use: \n\n```\n@software{cdiggins/type-inference,\n  author = {Diggins, Christopher},\n  title = {Type Inference for Polymorphic Types},\n  url = {https://github.com/cdiggins/type-inference},\n  year = {2017},\n}\n```\n\n# Motivation\n\nTypes for higher-order functions using generics can become more complex than the implementations of the functions. \nIf the types of generic (polymorphic) functions can be entirely inferred, then it can make the language\neasier to learn, read, and teach. \n\nThe following screen shot demonstrates the inferred types for a number of higher-order functions \nfor the [Heron standard library](https://github.com/cdiggins/heron-language/blob/master/input/array.heron). \n\n![image](https://user-images.githubusercontent.com/1759994/236684768-3c352cc6-4abd-4add-a35d-247caa978e62.png)\n\nIn data-flow visual languages it is not practical to expect a user to provide types, so type inference \nis a critical feature to achieve good performance. In 3ds Max the \n[Max Creation Graph - MCG](https://help.autodesk.com/view/3DSMAX/2017/ENU/?guid=GUID-608EC963-75ED-4F63-96B7-D8AE57E75959) feature \nis a visual data-flow programming language primarily used for creating procedural geometry and modifiers. \nMCG supports higher-order functions to represent control-flow, and performs type-inference on the fly. \n\n![image](https://user-images.githubusercontent.com/1759994/236686865-541cd729-574a-4dad-91a2-2a34d41a2347.png)\n\nFollow [this link for more information on higher order functions in MCG](https://help.autodesk.com/view/MAXDEV/2022/ENU/?guid=MAXDEV_MCG_arrays_and_functions_apply_and_bind_html).\n\n# History\n\nThis algorithm came from earlier work done on developing a type system for functional stack-based languages which\nwas documented in two technical reports. \n\n```\n@article{article,\n  author = {Diggins, Christopher},\n  year = {2008},\n  month = {05},\n  pages = {},\n  title = {Typing Functional Stack-Based Languages},\n  volume = {33}\n}\n```\n\n```\n@article{article,\n  author = {Diggins, Christopher},\n  year = {2008},\n  month = {01},\n  pages = {},\n  title = {Simple Type Inference for Higher-Order Stack-Oriented Languages}\n}\n```\n\n# Source Code and Dependencies \n\nAll of the source code is constained in a single TypeScript file [type_inference.ts](https://github.com/cdiggins/type-inference/blob/master/type_inference.ts). The tests are contained in the file [test.ts](https://github.com/cdiggins/type-inference/blob/master/test.ts). The tests have a dependency on the [Myna parser](https://github.com/cdiggins/myna-parser).\n\n# Implementation Overview\n\nThe basic algorithm is as follows:\n\n* Step 1 (renaming): Uniquely name all type variables \n* Step 2 (forall inference): Infer the position of all forall quantifiers in type arrays\n* Step 3 (unify constraint): Given a constraint between two types find a unifier for each variable.\n* Step 4 (substitution): Given a type signature compute a new signature by subsituting all type variables in the given type signature with the appropriate unifying type.\n* Step 5 (forall inference): Infer the position of all forall quanitifer in the resulting type.\n\nDuring step 4 (substitution), if a polytype is substituted for a type variable more than once each subsequent instance is given a new set of names for the generic type parameters. This is key for the step 5 to be able to correctly assign the forall quantifiers to the right level of nesting. \n\n## Implementation Details \n\nTypes are represented by the `Type` class. There are three kinds of types derived from this class: \n\n1. `TypeConstant` - Represents monotypes (simple non-plymorphic types)\n2. `TypeVariable` - Represents a universally quantified type variable, also known as a generic type parameter. \n3. `TypeArray` - A fixed-size collection of types that may or may not have universally quantified type parameters\n\nOther kinds of types, like functions and arrays, are encoded as special cases of type arrays. \n\n## Recursive Types\n\nRecursive types are not supported but will be reported by the system when inferred. They are identified by a type pair with the special name `Rec` and the depth of the recursive relation as an integer. For example: `(Rec 0)`. Any type containing a `Rec` form should be considered illegal, and will not unify correctly, but the algorithm will nonetheless detect recurrence relations in type relations and report them as if they were a valid type. \n\n## Type Constants \n\nA type constant is an atomic non-polymorphic type. It can be an identifier (e.g. `Num` or `Bool`), a number (e.g. `0` or `42`), or a symbol (i.e. `-\u003e` or `[]`). The symbols and numbers have no special meaning to the type inference algorithm and are just treated the same as ordinary type constants with identifiers. Type constants can be be used as unifiers associated with type variables and two type cosntants can't be unified together if they are different.\n\n## Type Arrays\n\nA type array is an ordered collection of 0 or more types. It is written out as a sequence of type expressions seprated by whitespace and surrounded by parentheses. Some example are: `()`, `(Num Bool)`, `(a [])`, `(b -\u003e c)`, `(a (b (c)))`. \n\nA type array may be monomorphic or polymorphic (i.e. have type parameters). Using symbolic type constants in a type array are used as a way of making them more readable and to help a language implementation associate different meaning to certain type arrays. For example: `(Num Num -\u003e Bool)` can be used to represent the function type that takes two numbers and returns a boolean value, and the `((Num []) [])` can be used to represent an array of arrays of numbers. \n\n## Polymorphic Types \n\nA polymorphic type (aka polytype, type scheme, generic type, or parametric type) is a type array that has one or more type parameters that are universally quantified. Type parameters are bound to universal quantifiers, represented as a `!` in the syntax. \n\nFor example: `!a.(a [])`, `!a!b.(pair a b)`, `!a!b.((a []) Num (a -\u003e b) -\u003e (b []))`\n\n## Type Variables \n\nType variables are the free occurrences of a type parameters that can be replaced a valid type expression. A type variable must be contained polymorphic type that contains the type parameter. \n\n# Type Encodings \n\nIn a practical setting it is important to encode many different kinds of types such as functions, tuples, structs, arrays, and more. These can all be described using type arrays with special type constants, and the type inference algorithm can deal with them seamlessly. In this section several different encodings are described. \n\n## Encoding Function Types \n\nA function is encoded as a type array with 3 elements: the first element representing the arguments, the second element is the special type constant `-\u003e` and the third is a type that represents the function return types.\n\nFor example the type `((Num Num) -\u003e Bool)` represents a function that converts a type array of two `Num` types to a single `Bool` type. \n\n## Encoding Type Lists \n\nA type list is encoded a pair of types (a type array of two elements) where the first element is the head of the list, and the second element is the rest of the list. Usually the type in the last position is a type variable which enables two type lists with different number of elements to be unified. \n\n## Encoding Array Types\n\nAn array type (not to be confused with an array of types) can be encoded as a type array of two elements: the type of elements in the followed by the special type constant `[]`.\n\nFor example: `!T.((T []) -\u003e Num)` encodes the type of a function that accepts convert \n\n# Top-Level Type Operators\n\nThere are three top-level type operators provided:\n\n1. Application  - Given a function type, and the type of the arguments, returns the output type.\n2. Composition  - Given two functions types (f and g) returns a new type representing the composition of the two functions. \n3. Quotation    - Given a type x generates a row-polymorphic function that will take any type (a) and return the pair a and x.\n\n# Row Polymorphism\n\nTwo sequences of types of different lengths can be unified if they are encoded as type lists with a type variable in the tail position.\n\nThis is useful to express concepts like a function that accept all tuples that have at least one or two items, such as a `dup`, `pop`, or `swap` operation that works on stacks. The type then would be written as:\n\n```\npop  : !a!S.((a S) -\u003e S)))\ndup  : !a!S.((a S) -\u003e (a (a S)))\nswap : !a!b!S.(a (b S) -\u003e (b (a S)))\n```\n\nNote that lower case and upper case variables do not have different syntactic, if a variable is in a tail position it is implicitly row polymorphic because of the nature of unification of lists encoded as nested pairs.\n\n# How the Algorithm differs from Hindley Milner \n\nIn Hindley Milner type inference all forall quantifiers are lifted to the top of a function. This is not immediately obvious when looking at common terms in the Lambda Calculus, but it becomes more obvious when we start looking at concatenative languages, i.e. stack based language with higher order functions, and consider the type of the term `quote dup`, or any term that makes a copy of a polymorphic function argument. \n\nConsider the following operators which are common in concatenative (i.e. functional stack based) languages:\n\n```\napply   : !R.(!S.((S -\u003e R) S) -\u003e R)\nquote   : !S!a.((a S) -\u003e (!R.(R -\u003e (a R)) S))\ncompose : !A!C!S.(!B.((B -\u003e C) ((A -\u003e B) S)) -\u003e ((A -\u003e C) S))\n```\n\nThe `apply` function applies the function on the top of the stack to the rest of the stack. The `quote` function remove a value from the top of the top of the stack, and pushes a function on to the stack that will take any stack and push that value back onto the stack, and the compose function will take two functions off of the stack (say `f` on top followed by `g` underneath) and then push the composition of g with f (`g.f`) back onto the stack. \n\nNow consider the type inferred by the algorithm for the term `quote dup`. Notice that there are two independent forall quantifiers in the generated function. This is not possible for HM type inference.\n\n```\nquote dup : !t0!t1.((t0 t1) -\u003e (!t2.(t2 -\u003e (t0 t2)) (!t3.(t3 -\u003e (t0 t3)) t1)))\n```\n\nWhen we infer the type for the term `quote dup apply` we get the following: \n\n```\nquote dup apply : !t0!t1.((t0 t1) -\u003e (t0 (t0 (t1)))\n```\n\nNotice this is exactly the same type as for `dup` which is what we expect intuitively.  \n\n## Compared to Haskell \n\nThe core stack terms can all be described easily in standard Haskell and with the type inferred, but the forall quantifiers are all implicitly lifted to the top level. Here is an example of an interactive session with Haskell \n\n```\nGHCi, version 8.2.1: http://www.haskell.org/ghc/  :? for help\nPrelude\u003e :set +t\nPrelude\u003e dup (a, s) = (a, (a, s))\ndup :: (a, b) -\u003e (a, (a, b))\nPrelude\u003e pop (a, s) = s\npop :: (a, b) -\u003e b\nPrelude\u003e swap (a, (b, s)) = (b, (a, s))\nswap :: (a1, (a2, b)) -\u003e (a2, (a1, b))\nPrelude\u003e quote (a, s) = (\\r -\u003e (a, r), s)\nquote :: (a, b1) -\u003e (b2 -\u003e (a, b2), b1)\nPrelude\u003e apply (f, s) = f s\napply :: (t1 -\u003e t2, t1) -\u003e t2\nPrelude\u003e compose (f, (g, s)) = (g . f, s)\ncompose :: (a -\u003e b1, (b1 -\u003e c, b2)) -\u003e (a -\u003e c, b2)\n```\n\nSo far everything looks the same as before, however the type of the expression `dup quote` in a concatenative language (`quote . dup` when expressed in a prefix applicative function language) the type inferred by Haskell is as follows: \n\n```\nPrelude\u003e quotedup = dup . quote\nquotedup :: (a, b) -\u003e (b2 -\u003e (a, b2), (b2 -\u003e (a, b2), b))\n```\n\nThis type represents the two `quote` functions on the stack as being linked, they both require the same stack configuration `b2`. So when we are to try and compose this with apply, the type inference algorithm fails apart as expected with prenex polymorphism.\n\n```\nPrelude\u003e f = apply . quotedup\n\n\u003cinteractive\u003e:11:13: error:\n    * Occurs check: cannot construct the infinite type:\n        t1 ~ (t1 -\u003e (a, t1), b)\n      Expected type: (a, b) -\u003e (t1 -\u003e (a, t1), t1)\n        Actual type: (a, b) -\u003e (t1 -\u003e (a, t1), (t1 -\u003e (a, t1), b))\n    * In the second argument of `(.)', namely `quotedup'\n      In the expression: apply . quotedup\n      In an equation for `f': f = apply . quotedup\n    * Relevant bindings include\n        f :: (a, b) -\u003e (a, t1) (bound at \u003cinteractive\u003e:11:1)\n```\n\n# Appendix: An Alternate Syntax of Type Signatures\n\nIn the test system an alternate type syntax is proposed that allows the forall quantifiers to be ommitted and a syntactic analysis to identify type variables. Apostrophes are placed in front of identifiers to identify them as variables. Variables are assumed to be uniquely named, and the appropriate polytype is assumed. \n\nFor example a function type `!a!b.((a b) -\u003e (!c.(c -\u003e (a c) b))` can be expressed as `(('a 'b) -\u003e (('c -\u003e 'a 'c) 'b))`.\n\n## Inference of Location of For-all Quantifiers\n\nGiven a type array containing type variables assumed to be uniqely named in potentially nested type arrays, the algorithm will infer which type parameters belong to which type arrays. In other words the precise location of the for-all quantifiers are determined. This process is done by assigning each type variables as a paraemter to the inner-most type array that contains all references to that type variable. This is done in the function `computeParameters()`.\n\n# For More Information\n\n* https://www.researchgate.net/publication/228985001_Typing_Functional_Stack-Based_Languages\n* https://prl.khoury.northeastern.edu/blog/static/stack-languages-annotated-bib.pdf\n* https://en.wikipedia.org/wiki/Parametric_polymorphism. \n* https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system\n* https://en.wikipedia.org/wiki/Lambda_calculus\n* https://en.wikipedia.org/wiki/Typed_lambda_calculus\n* https://en.wikipedia.org/wiki/Unification_(computer_science) \n* https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)\n* https://www.cs.cornell.edu/courses/cs3110/2011sp/Lectures/lec26-type-inference/type-inference.htm\n* http://www.angelfire.com/tx4/cus/combinator/birds.html\n* https://github.com/leonidas/codeblog/blob/master/2012/2012-02-17-concatenative-haskell.md\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcdiggins%2Ftype-inference","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcdiggins%2Ftype-inference","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcdiggins%2Ftype-inference/lists"}