Type Inference Algorithm
Elara implements a complete Hindley-Milner type inference system with support for let-polymorphism, higher-kinded types, and limited higher-rank types. The type inference engine automatically deduces types for all expressions without requiring explicit type annotations.Overview
The type inference system follows the classic constraint-based approach:Type System Foundations
Monotypes and Polytypes
Elara distinguishes between two kinds of types:-
Monotypes (
τ): Simple, non-polymorphic types- Type variables:
α,β - Type constructors:
Int,String,Bool - Function types:
τ₁ → τ₂ - Applications:
List α,Option Int
- Type variables:
-
Polytypes (
σ): Polymorphic types with universal quantification∀α. α → α(identity function)∀α β. (α → β) → List α → List β(map function)
Let-Polymorphism: Only let-bound variables can have polytypes. Lambda parameters are always monomorphic (this is a key difference from System F).
Type Variables
The inference engine uses two kinds of type variables:α₁, α₂, ...) are generated fresh during inference and represent unknown types. Skolem variables are introduced when checking against user-provided type signatures and must not be unified with other types.
Constraint Generation
The constraint generation phase traverses the Shunted AST and generates type equality constraints.Core Rules
Variable (VAR)
Variable (VAR)
Abstraction (ABS)
Abstraction (ABS)
α for the parameter, extend the environment, and infer the body type τ.Application (APP)
Application (APP)
β, and emit constraint τ₁ ∼ τ₂ → β.Let Binding (LET)
Let Binding (LET)
Pattern Matching (MATCH)
Pattern Matching (MATCH)
τ, and all branches must return the same result type τᵣ.Constructor Patterns
When type-checking constructor patterns, the system:- Looks up the constructor’s signature (e.g.,
Some : ∀α. α → Option α) - Instantiates the polytype with fresh unification variables
- Matches the constructor’s argument types against the pattern arguments
- Emits equality constraints
Constraint Solving (Unification)
The solver processes constraints to find a substitutionS that makes all equalities hold.
Unification Algorithm
The unification algorithmunify(τ₁, τ₂) is defined recursively:
Identical Variables
Identical Variables
Variable and Type
Variable and Type
Type Constructors
Type Constructors
Function Types
Function Types
Type Alias Expansion
When unification fails between type constructors, the system attempts to expand type aliases:Recursive type aliases are supported and essential for expressing types like:
Generalization
Generalization converts a monotype to a polytype by quantifying over free type variables:Value Restriction
Elara implements the value restriction: only syntactic values (lambdas, constructors) can be generalized. This prevents issues with side effects:Instantiation
Instantiation replaces quantified variables with fresh unification variables:β₁, ..., βₙ are fresh.
Error Reporting
The type checker provides detailed error messages with context:Type Mismatch
Type Mismatch
Occurs Check Failure
Occurs Check Failure
Constructor Arity Mismatch
Constructor Arity Mismatch
Implementation Details
Source Location
The implementation atsrc/Elara/TypeInfer/ConstraintGeneration.hs includes:
- Constraint generation:
generateConstraints :: ShuntedExpr -> Eff (TypedExpr, Monotype) - Unification:
unify :: Monotype -> Monotype -> Eff (Constraint, Substitution) - Solver:
solveConstraint :: Constraint -> Set UniqueTyVar -> Constraint -> Eff (Constraint, Substitution)
Optimization
The type inference engine achieves > 300% performance improvement through:- Efficient union-find for type variable representatives
- Memoization of constraint solving results
- Lazy constraint generation (integrated with Rock query system)
Further Reading
- Compiler Internals - AST transformations
- Language Specification - Formal type system rules
- Damas, L. & Milner, R. (1982). Principal type-schemes for functional programs