Data type 2026-10-31
A classification of program values together with the operations permitted on them. An algebraic data type describes values by data constructors; a function type describes acceptable arguments and produced results.
Function type 2026-10-13
A function type classifies functions that accept arguments of type and produce results of type if they return. For example, has function type in our teaching programming language, while has function type . A function type specifies how a function may be used; it does not guarantee that a call returns.
The two central typing judgment rules areThe lambda abstraction rule checks the body under an extended typing context; the lambda application rule checks that the argument has exactly the required type. These rules describe our simple type examples. [ref]
https://softwarefoundations.cis.upenn.edu/plf-current/Stlc.html. Software Foundations, Programming Language Foundations, The Simply Typed Lambda-Calculus: typing rules
For , assume ; both and have type
int, so has type int. The abstraction therefore has function type . Since has type int, the call has type int and evaluates to . Replacing by true fails the argument requirement. This local derivation explains each step without running the program.Arrows associate to the right: means , a function returning another function. For instance, has function type . Supplying one integer yields a function awaiting the second. Functions can also be arguments: accepts an integer-to-integer function and has an integer result type.
// Target: computer-science.bigb
Hindley-Milner type system 2026-10-13
The pure Hindley-Milner type system combines function types, type inference and let polymorphism. It solves constraints by type unification and generalizes eligible type variables at
let bindings. Robin Milner's 1978 paper presents Algorithm W and proves its soundness for the paper's language; it also acknowledges Roger Hindley's earlier work on finding general types. [ref] Kind (type theory) 2026-10-31
A classification of type-level expressions. In the traditional Haskell notation, ordinary types have kind
*, while a unary type constructor such as Tree has kind * -> *. Applying it to a suitable type argument produces an ordinary type. This distinguishes the type-level application from applying a value with a function type.Choose as the domain of the new function type. Since , the assumption is consistent with and deleting leaves unchanged. The lambda abstraction rule givesUse the universal typing axiom for , then the lambda application rule:ThereforeThe condition is about the typing context, not the free variables of . Some occurrences of in might have been hidden by a use of the universal typing axiom; the derivation above still follows the printed rules.
Begin with the variable typing judgment . Because is absent from this typing context, the lambda abstraction rule permits to have type :Assign the entire lambda application type using the universal typing axiom, without attempting to assign a function type to . ThenAlthough is a free variable of the body, it is absent from its recorded typing context. Consequently the lambda abstraction rule allows any simple type for . Abstract over and then :The side condition for holds because is absent; the side condition for holds because its recorded simple type is . Thus
The exact restriction in the ordinary simply typed lambda calculus isThus the unrestricted choice of available in part (c) fails. A reading of the printed sentence as asserting impossibility for every choice of is false. This issue occurs in the original PDF as well as its transcription.[ref]
To prove necessity, first observe why the ordinary rules cannot hide assumptions. Structural induction on a derivation provesFor a variable, both sides are the singleton containing that variable. For lambda application, both sides are the union of the two premise supports. For lambda abstraction, both sides delete the bound variable. There is no universal typing axiom to interrupt this equality.
Now invert the proposed derivation. Each outer lambda abstraction must have been introduced by the abstraction rule, since the other ordinary rules conclude a variable or a lambda application. The body must therefore have the typing judgmentThe support equality just proved ensures that both displayed assumptions occur; consistency fixes their simple types to those specified by the outer function type. Inverting the lambda application gives a simple type for its argument and the following premises:There is no flexibility in the supports: has the single free variable , whereas has both free variables . Invert the second lambda application. Its two premises must befor some simple type . The variable rule is the only possible final rule for each premise. It forces and , yielding .
Conversely, suppose . The ordinary rules giveThe typing contexts are consistent, so another lambda application gives the body type . Two lambda abstractions discharge and , establishingThis proves sufficiency and supplies a family of counterexamples to the literal impossibility claim. For a concrete failure of the unrestricted claim, choose and as atomic type variables. The type variable is not a function type , so no derivation exists for that fixed pair. Here the atomic type variables are fixed type expressions, rather than unknowns that one may change during type inference.
The reason for the restriction is the otherwise discarded lambda application : the ordinary rules still type-check it. Its subsequent disappearance under beta reduction does not license reversing that beta reduction while preserving every simple type of the result. The beta-normal form admits arbitrary , whereas the unreduced lambda term has the additional constraint just derived.
Past exam of the mathematics course of the University of Cambridge 2007 ia Paper 5 10 a Solution Created 2026-09-24 Updated 2026-10-14
Represent the two constructors of the type grammar by subclasses of The nested classes are public for use by clients, while their fields are private;
MLType: a TypeVar node and an Arrow node containing two types. Use immutable fields so a completed type expression cannot acquire cycles through later mutation. The static factory allocates a fresh object and gives it a fresh identifier shared across the whole hierarchy:import java.math.BigInteger;
public abstract class MLType {
private MLType() { }
private static BigInteger next = BigInteger.ONE;
public static synchronized TypeVar createNewTypeVar() {
TypeVar answer = new TypeVar(next);
next = next.add(BigInteger.ONE);
return answer;
}
public static final class TypeVar extends MLType {
private final BigInteger id;
private TypeVar(BigInteger id) { this.id = id; }
}
public static final class Arrow extends MLType {
private final MLType from, to;
public Arrow(MLType from, MLType to) {
if (from == null || to == null)
throw new IllegalArgumentException();
this.from = from; this.to = to;
}
}
}TypeVar can be created only through the factory. Client code uses MLType.TypeVar and MLType.Arrow. A type variable is identified by its unique node, and a function type by its domain and codomain nodes; repeated occurrences of one logical variable share the same TypeVar object.The monotonically increasing
BigInteger identifier avoids fixed-width counter overflow, and synchronization prevents two simultaneous calls from receiving the same identifier. Actual allocation remains subject to available memory. This is an abstract syntax tree representation, possibly sharing subexpressions. Fresh variable nodes and recursive arrow nodes represent every type in the given grammar.Both uses of
x in this definition have the same monomorphic type within the body. Write x :: u -> v. The inner application x z forces z :: u and has type v; the outer application x (x z) forces v = u. Thus x :: u -> u. Finally, x (x z) is applied to y, so u must itself be a function type, say a -> b. This givesf :: ((a -> b) -> a -> b) -> a -> (a -> b) -> b
f x y z = x (x z) yThe principal type is thereforeThe argument
x transforms a function into another function of the same type. The body transforms z twice, then applies the resulting function to y; no recursive type equation is required.A simple current-value implementation works when is a pure function, but sharing a mutable closure between copies would violate the independence requirement used in part (d). Use a memoized successor chain for stateful iterator copies: the generated sequence has shared cells, and each iterator has its own current-cell pointer.The representation invariant is that
object FunIter {
final class Cell[T](val head: T, f: T => T) {
lazy val tail: Cell[T] = new Cell[T](f(head), f)
}
}
class FunIter[T](v: T, f: T => T) extends Iter[T] {
private var cursor = new FunIter.Cell[T](v, f)
def hasNext: Boolean = true
def next(): T = {
val x = cursor.head
cursor = cursor.tail
x
}
def copy(): Iter[T] = {
val result = new FunIter[T](cursor.head, f)
result.cursor = cursor
result
}
}cursor points to the next cell to be returned, and each cell's lazy tail is computed at most once. The abstraction function is the infinite sequence of heads starting at that cell. Advancing changes only this iterator's pointer. A copy starts at the same cell, so both observe the same remaining heads even when their calls are interleaved:For a stateful , the generator state must be privately owned by this shared chain; unrelated external calls to must not change it. As usual, the values and transition must allow successive results to be observed without externally mutating old results. No interface can independently clone the hidden state of an arbitrary externally mutated closure from its function type alone. The chain avoids needing such cloning: a lagging copy reuses the already generated future instead of invoking again.Tree is a type constructor, taking the element type parameter a to the algebraic data type Tree a. It is not a value and has no ordinary value-level function type; its kind of a type is * -> *. The other names are data constructors with polymorphic type signatures:One :: a -> Tree a
Two :: Tree a -> Tree a -> Tree aOne constructs a leaf-labelled binary tree with one leaf; Two joins two such trees under a new internal node. Unlike a general binary tree representation with empty child slots, this representation has no empty tree.[ref]Take compatible function types
f :: b -> c, g :: b -> a -> b, and h :: c -> a -> c. The necessary conditions obtained from the two induction base cases are strictness of f and the fusion equationAt zs = [], the desired equation is exactly the displayed fusion equation. At zs = undefined, both left folds diverge because their list argument has no outer constructor; the equation therefore requires f undefined = undefined. This is the strict function condition. If only finite total spines were considered, the first condition would not be necessary.These conditions also support the step. The induction hypothesis must hold for every accumulator and element. For The middle rewrite uses the fusion equation at
z:zs, instantiate that hypothesis at g x y and z:f (foldl g (g x y) (z:zs))
= f (foldl g (g (g x y) z) zs)
= foldl h (h (f (g x y)) z) zs
= foldl h (h (h (f x) y) z) zs
= foldl h (h (f x) y) (z:zs).x,y. For pure computable functions, equality on infinite spines follows from finite approximations as in part (a).Change the right fold's result type to an accumulator-transforming function type:Thus
foldlAsFoldr :: (b -> a -> b) -> b -> [a] -> b
foldlAsFoldr s n xs = foldr c id xs n
where
c x k a = k (s a x)c x k = \a -> k (s a x) and k = id in the question's notation. Here c :: a -> (b -> b) -> (b -> b), and the fold's base value is the identity function, not the initial accumulator n.The recursion invariant is At an undefined spine, applying the undefined folded function also gives the bottom value. The finite-approximation argument then covers infinite lazy lists. This is a continuation representation of a left fold: the result retains the operation still to be performed, avoiding the information loss in part (d).
foldr c id xs a = foldl s a xs for every a. For [], both sides equal a. For a cons:foldr c id (x:xs) a
= c x (foldr c id xs) a
= foldr c id xs (s a x)
= foldl s (s a x) xs
= foldl s a (x:xs). Programming language Created 2026-10-13 Updated 2026-10-14
A programming language specifies how programs are written and what their execution means. Its type system describes which operations may be combined; an abstract syntax tree records the structure on which these rules act. The examples under type system use a small teaching language with integers, Boolean values and function types.
// Target: computer-science.bigb
Simple type Created 2026-10-13 Updated 2026-10-24
In the examples here, a simple type is a finite expression built from the base types
int and bool using function types:For example, describes a function accepting an integer and producing a Boolean value. During type inference, the grammar is extended with type variables such as for types not yet determined. A type scheme additionally binds such type variables; it is distinct from the individual simple type used at one call. Tree fold Created 2026-10-24 Updated 2026-10-31
A structural recursion replacing the empty binary tree by a value and a nonempty node by a combining function on its two folded children and root label. The result type may itself be a function type, allowing a fold to construct a membership predicate or a difference list rather than traversing all branches for every subsequent query.
More generally, choose one operation for each data constructor. For a leaf-labelled binary tree represented by
One a | Two (Tree a) (Tree a), the tree fold instead has type signature (a -> b) -> (b -> b -> b) -> Tree a -> b: one operation handles the leaf label, and the other combines the two folded children. Replacing the operations by the original data constructors reconstructs the input, as structural induction proves. Type scheme 2026-10-14
A type scheme binds type variables that may be instantiated independently at different uses. The identity function has type scheme : choose one type per use, then use that same choice on both sides of its function type. A free type variable shared with the typing context is not automatically such an independently selectable parameter.
Type system 2026-10-13
A type system assigns types to program expressions and supplies rules governing their use. A type constrains permitted operations: in our teaching programming language,
int admits integer addition, bool supplies true and false for conditions, and a function type specifies an argument type and a result type. Storage as bits alone does not explain these distinctions; the type system determines whether an expression can serve as an argument, a condition or an operand.A typing judgment says that expression has type under the assumptions in the typing context . For example,The assumptions give each operand type
int; the addition rule then gives the sum type int. Changing to type bool prevents this derivation. Our teaching programming language has distinct int and bool types and no implicit conversions between them.The conditional rule requires a Boolean condition and the same type for both branches:Thus
if true then 1 else 2 has type int, while if true then 1 else false is rejected: its branches would require and simultaneously. Even a branch that will not execute is checked by this rule. This is a rule of this particular type system; a different programming language can choose different rules. [ref]https://softwarefoundations.cis.upenn.edu/plf-current/Types.html. Software Foundations, Programming Language Foundations, Type Systems: typing relation
The useful feature is compositional reasoning: after checking the parts, a rule explains how to check the whole. The function type diagram shows this for a call. Type inference can discover the required types automatically, and type variables let one express relationships that recur across many concrete types. The result is both a practical early error detector and a small, readable derivation explaining why an expression fits its intended use.
The guarantee depends on the rules and execution model. Type safety can exclude specified failures such as adding a Boolean or calling an integer as a function. It does not by itself establish every desired runtime property or guarantee that execution terminates. A type system that permits unrestricted recursion can accept a computation that runs forever; exception handling also needs its own account of permitted outcomes.
Type unification Created 2026-10-14 Updated 2026-10-25
Type unification finds a type substitution making specified type expressions equal, or reports incompatible constraints. In our simple type grammar, matching two function types reduces to matching their argument types and their result types. Distinct base types
int and bool fail to match; solving a type variable against a nontrivial type expression uses the occurs check.// Target: computer-science.bigb
Type variable 2026-10-13
A type variable is a placeholder for a type. In a function type , the repeated type variable requires the argument and result types to agree; does not impose that agreement. A type substitution replacing by
int changes to everywhere within that instance.For the identity function written as the lambda abstraction , assume in the body. Looking up gives result type , so the abstraction rule gives . In the pure Hindley-Milner type system,
let identity = λx.x in ... gives identity the type scheme by let polymorphism. Each use receives fresh type variables: identity 3 chooses int for its instance, and identity true chooses bool for a separate instance. These choices do not change the original type scheme.Independent type instantiations of a let-bound identity function
. The type scheme stays general while two uses receive different type substitutions. Each use preserves the equality of its own argument and result types. Details.For example,
let identity = λx.x in let a = identity 3 in identity true is valid and has type bool. By contrast, (λf. let a = f 3 in f true) (λx.x) is rejected by the Hindley-Milner type system: the lambda abstraction binds f to one monomorphic function type. The first call requires its argument type to be int, and the second requires that same type to be bool. Naming the first result with let a does not generalize f. The distinction makes reusable code possible while keeping the assumptions about a supplied argument consistent.// Target: computer-science.bigb

