In computer programming languages, a recursive data type (also known as a recursively-defined, inductively-defined or inductive data type) is a data type for values that may contain other values of the same type. Data of recursive types are usually viewed as directed graphs.
An important application of recursion in computer science is in defining dynamic data structures such as Lists and Trees. Recursive data structures can dynamically grow to an arbitrarily large size in response to runtime requirements; in contrast, a static array's size requirements must be set at compile time.
Sometimes the term "inductive data type" is used for algebraic data types which are not necessarily recursive.
An example is the list type, in Haskell:
This indicates that a list of a's is either an empty list or a cons cell containing an 'a' (the "head" of the list) and another list (the "tail").
Another example is a similar singly linked type in Java:
This indicates that non-empty list of type E contains a data member of type E, and a reference to another List object for the rest of the list (or a null reference to indicate that this is the end of the list).
Data types can also be defined by mutual recursion. The most important basic example of this is a tree, which can be defined mutually recursively in terms of a forest (a list of trees). Symbolically: f: [t[1], ..., t[k]] t: v fA forest f consists of a list of trees, while a tree t consists of a pair of a value v and a forest f (its children). This definition is elegant and easy to work with abstractly (such as when proving theorems about properties of trees), as it expresses a tree in simple terms: a list of one type, and a pair of two types.
This mutually recursive definition can be converted to a singly recursive definition by inlining the definition of a forest: t: v [t[1], ..., t[k]]A tree t consists of a pair of a value v and a list of trees (its children). This definition is more compact, but somewhat messier: a tree consists of a pair of one type and a list another, which require disentangling to prove results about.
In Standard ML, the tree and forest data types can be mutually recursively defined as follows, allowing empty trees:
data Forest a = Nil | Cons (Tree a) (Forest a)
In type theory, a recursive type has the general form μα.T where the type variable α may appear in the type T and stands for the entire type itself.
For example, the natural numbers (see Peano arithmetic) may be defined by the Haskell datatype:
In type theory, we would say:
nat=\mu\alpha.1+\alpha
\mu\alpha.1+\alpha
There are two forms of recursive types: the so-called isorecursive types, and equirecursive types. The two forms differ in how terms of a recursive type are introduced and eliminated.
With isorecursive types, the recursive type
\mu\alpha.T
T[\mu\alpha.T/\alpha]
X[Y/Z]
roll:T[\mu\alpha.T/\alpha]\to\mu\alpha.T
unroll:\mu\alpha.T\toT[\mu\alpha.T/\alpha]
Under equirecursive rules, a recursive type
\mu\alpha.T
T[\mu\alpha.T/\alpha]
Isorecursive types capture the form of self-referential (or mutually referential) type definitions seen in nominal object-oriented programming languages, and also arise in type-theoretic semantics of objects and classes. In functional programming languages, isorecursive types (in the guise of datatypes) are common too.[2]
In TypeScript, recursion is allowed in type aliases.[3] Thus, the following example is allowed.
However, recursion is not allowed in type synonyms in Miranda, OCaml (unless -rectypes
flag is used or it's a record or variant), or Haskell; so, for example the following Haskell types are illegal:
Instead, they must be wrapped inside an algebraic data type (even if they only has one constructor):
This is because type synonyms, like typedefs in C, are replaced with their definition at compile time. (Type synonyms are not "real" types; they are just "aliases" for convenience of the programmer.) But if this is attempted with a recursive type, it will loop infinitely because no matter how many times the alias is substituted, it still refers to itself, e.g. "Bad" will grow indefinitely: Bad
→ (Int, Bad)
→ (Int, (Int, Bad))
→ ...
.
Another way to see it is that a level of indirection (the algebraic data type) is required to allow the isorecursive type system to figure out when to roll and unroll.