A topic in the Open Knowledge Graph — a free, open map of 15,290 topics and the order to learn them in.

Refinement Types

Research Depth 95 in the knowledge graph ☐ I know this ☆ Set as goal
1topic build on this
514prerequisites beneath it
See this on the map →
Type Systems OverviewDependent Type Theory+1 more→→Liquid Types
liquid-types liquid-haskell smt predicate-refinement subtyping

Core Idea

Refinement types augment base types with logical predicates that constrain the set of values. Instead of just Int, you write {v : Int | v > 0} — the type of positive integers. A function div : Int -> {v : Int | v != 0} -> Int statically guarantees the divisor is nonzero. Unlike full dependent types, refinement predicates are restricted to decidable logics (typically quantifier-free theories handled by SMT solvers), enabling fully automatic type checking without manual proofs. Liquid types, the most successful refinement type system, infer refinement predicates automatically using abstract interpretation over types, combining the expressiveness of dependent-types-lite with the automation of type inference.

Explainer

Full dependent types (as in Coq or Agda) let types express arbitrarily precise specifications, but at a cost: type checking is undecidable and requires the programmer to construct proofs manually. At the other extreme, simple type systems (as in Java or Python) are fully automatic but cannot express properties like "this integer is positive" or "this list is sorted." Refinement types occupy a sweet spot: they extend base types with logical predicates, gaining significant expressive power while retaining automatic type checking.

A refinement type has the form {v : T | P(v)}, where T is a base type and P is a logical predicate. The type {v : Int | v > 0} contains exactly the positive integers. A function can declare `div : (x : Int) -> (y : {v : Int | v != 0}) -> Int`, making division by zero a static type error. Array access can use `get : (a : Array T) -> (i : {v : Int | 0 <= v && v < len(a)}) -> T`, making out-of-bounds access a type error. These specifications live in the type system, are checked at compile time, and carry zero runtime cost.

The key to automation is restricting predicates to decidable theories that SMT solvers can handle. Liquid types, developed by Rondon, Kawaguchi, and Jhala, restrict refinements to conjunctions of qualifiers — simple predicates from a fixed set — and use abstract interpretation to infer which qualifiers apply at each program point. Subtype checking reduces to SMT validity: {v : T | P} is a subtype of {v : T | Q} if and only if P implies Q, which the solver decides automatically. LiquidHaskell applies this to Haskell, enabling programmers to verify memory safety, termination, functional correctness, and information flow properties with minimal annotation burden. The tool infers most refinements automatically; the programmer adds annotations only where inference needs guidance.

Path sensitivity is crucial for practical refinement typing. Inside `if x > 0 then f(x) else g(x)`, the type of x in the then-branch is refined to {v : Int | v > 0} by the branch condition. The type checker tracks these path conditions and includes them in SMT queries. This means that idiomatic null checks, bounds checks, and error handling are automatically recognized by the type system. The programmer does not need to add explicit annotations for code that already checks its preconditions — the type system extracts the information from the control flow.

Refinement types are less expressive than full dependent types — they cannot express properties requiring quantifiers (like "for all elements in this list, P holds") without extensions. But for a large class of safety and correctness properties — array bounds, division by zero, resource protocol compliance, numeric invariants — they provide strong static guarantees with an experience closer to ordinary type checking than to theorem proving. Tools like LiquidHaskell, Flux (for Rust), and F* (which combines refinement and dependent types) demonstrate that refinement types can scale to substantial codebases while catching real bugs that conventional type systems miss.

Practice Questions 3 questions

Prerequisite Chain

Understanding Zero → The Number Zero → Counting to Five → Counting to 10 → Counting to 20 → Counting a Set of Objects Up to 20 → Cardinality: The Last Number Counted → Matching Numerals to Quantities → Subitizing Small Quantities → Addition Within 10 → Number Bonds to 10 → Addition Within 20 → Doubles and Near Doubles → Doubles Facts Within 10 → Near Doubles Facts Within 20 → Mental Math Strategies for Addition → Mental Math: Adding and Subtracting Tens → Addition Within 100 → Repeated Addition as Multiplication → Multiplication as Equal Groups → Multiplication: Arrays → Basic Multiplication Facts (0s, 1s, 2s, 5s, 10s) → Multiplication Facts Within 100 → Division as Equal Sharing → Division as Grouping (Measurement Division) → Division: Grouping (Repeated Subtraction) Model → Division: Fair Sharing Model → Division as Equal Sharing → Division as Grouping → Basic Division Facts → Division Facts Within 100 → Multiplication and Division Fact Families → Relationship Between Multiplication and Division → Division Facts as Inverse of Multiplication → Remainders and Quotients in Division → Division Word Problems → Multi-Step Word Problems → Solving Multi-Step Word Problems → Multiplication Word Problems → Division Word Problems → Introduction to Long Division → Factors and Multiples → Prime and Composite Numbers → Equivalent Fractions → Relating Fractions and Decimals → Decimal Place Value → Integers and the Number Line → Comparing and Ordering Integers → Absolute Value → Adding Integers → Subtracting Integers → Multiplying Integers → Introduction to Exponents → Order of Operations → Integer Order of Operations → Variable Expressions → The Distributive Property → Variables and Expressions Review → Introduction to Polynomials → Adding and Subtracting Polynomials → Multiplying Polynomials → Factorial → Permutations → Combinations → Counting Principles: Addition and Multiplication Rules → Introduction to Graph Theory → Propositional Logic Foundations → Logical Equivalences → Boolean Algebra → Boolean Type and Truth Values → Comparison Operators and Boolean Tests → Logical Operators and Boolean Algebra → Boolean Algebra and Fundamental Laws → Logic Gates Fundamentals → Implementing Boolean Functions with Gates → Karnaugh Map Simplification → Combinational Circuit Design → Flip-Flops and Latches → Finite State Machines (FSMs) → Deterministic Finite Automata (DFA) → Nondeterministic Finite Automata (NFA) → Two-Way Finite Automata → NFA to DFA Conversion (Subset Construction) → DFA Properties and Minimization Algorithms → Regular Languages: Definition and Characterization → Context-Free Grammars (CFGs) → Context-Free Grammar Properties and Ambiguity → Parse Trees, Derivations, and Ambiguity in CFGs → Context-Free Grammars in Compiler Design → Abstract Syntax Trees (ASTs) → Symbol Tables and Scope Resolution → Semantic Analysis Phase → Type Systems Overview → Curry-Howard Correspondence → Dependent Type Theory → Refinement Types

Longest path: 96 steps · 514 total prerequisite topics

Prerequisites (3)

Leads To (1)