Algebraic data types and pattern matching
An algebraic data type defines exactly how values of a nominal type can be constructed. Pattern matching safely recovers the information carried by a constructor.
Enumerations and empty types
type Direction = North + East + South + West;
type Void;Direction has four nullary constructors. Void has none, so a valid runtime value of Void cannot be constructed.
Constructors with fields
type Shape =
Circle * radius:Float64
+ Rectangle * width:Float64 * height:Float64;+ separates constructors. * introduces fields belonging to the preceding constructor. Construct values positionally or, where supported by the constructor, with named fields:
Circle(2.0)
Rectangle { width = 3.0, height = 4.0 }Named construction checks that every required field appears exactly once.
Type parameters and recursion
type Maybe(a:Type) = Nothing + Just * value:a;
type List(a:Type) =
Nil
+ Cons * head:a * tail:List(a);Parameters make one declaration reusable. Recursive occurrences describe inductive structure; positivity and required termination checks prevent unsound type-level recursion.
Implicit constructors
When a type body begins with a lowercase field, tulam creates one constructor with the type’s name:
type Point = x:Float64 * y:Float64;
value origin = Point(0.0, 0.0);There is no record declaration keyword. Nominal record-like data uses type.
Matching fields
function area(shape:Shape) : Float64 = match shape
| Circle * radius -> 3.14159 * radius * radius
| Rectangle * width * height -> width * height;Named patterns improve clarity when order is not the main idea:
Point { x = px, y = py }Nested patterns decompose several layers at once. Use a wildcard _ for a field whose value is irrelevant.
Spread and derived instances
type Point3D = ..Point * z:Float64;
type Color = Red + Green + Blue deriving Eq, Show;Spread copies visible nominal fields in order; it is layout composition, not inheritance or an open structural row. deriving requests ordinary checked instances from derivation logic supplied by the algebra. It does not bypass coherence or laws.
Dependent and generalized constructors
Later fields may refer to earlier ones, and a constructor may state a refined result type:
type Sized(a:Type) = size:Nat * items:Vec(a,size);
type Vec(a:Type,n:Nat) =
VNil : Vec(a,Z)
+ VCons * head:a * tail:Vec(a,n) : Vec(a,Succ(n));Matching a GADT constructor refines indices for its branch. Chapters 16 and 17 explain the dependent reasoning involved.
Choosing data or classes
Use algebraic data when a known family of alternatives and exhaustive matching are central. Use a class when nominal extension and dynamic dispatch are central. Neither is a disguised version of the other.
Common mistakes
Do not use commas between constructors or constructor fields: + and * own those roles in a type declaration. Do not confuse nominal spread with an open structural row, and do not omit a constructor branch from a closed match.
Recap
A data declaration fixes a nominal set of constructors. Construction proves which alternative exists; exhaustive matching recovers its fields safely and may refine dependent indices.
Normative details: Language Reference §5.