Matrices
A matrix is a two-dimensional lookup table with typed axes and typed cells. It maps pairs of values to a body value, checked at compile time and generated as nested maps in every target language.
Syntax
A matrix declaration has four parts: a name, two axis types, a body type, and a grid of values.
name: XAxisType, YAxisType.
BodyType, x1, x2, x3
y1, b11, b12, b13
y2, b21, b22, b23
The header line declares the matrix name and its two axis types, separated by a comma and terminated with a period. The first indented row begins with the body type, followed by the X-axis values. Each subsequent row begins with a Y-axis value, followed by the body cells for that row.
Basic Example
A boolean matrix mapping union tags to types:
union Bar
wat
frog
union Baz
zip
zam
allowed: Bar, Baz.
Boolean, wat, frog
zip, true, false
zam, false, true
This creates a lookup from (Bar, Baz) pairs to Boolean values. allowed[wat][zip] is true, allowed[frog][zam] is true, and so on.
Set Bodies
The body type can be a set, written with braces. This is common for permission matrices where each cell holds a set of allowed operations:
union MFoo
bar
baz
perm: Type, Type.
{MFoo}, Integer, Unit
Text, {bar}, {baz}
Boolean, {baz}, {baz}
Unit, {bar}, {bar}
Set literals use braces: {bar} is a singleton set, {1, 2, 3} is a set of integers, and {} is the empty set.
The wildcard {_} means "all members of the set type":
permissions: Role, Type.
{CRUD}, admin, operator, customer
Pet, {_}, { read, update }, { read }
Here {_} expands to { create, read, update, delete, list } — every tag in the CRUD union.
Optional Bodies
The body type can be optional, allowing () (unit/null) as a cell value:
scores: Type, Type.
Integer?, Integer, Unit
Text, 2, 8
Boolean, 5, ()
Unit, 1, 4
Optional sets and sets of optionals are distinct:
| Body Type | Meaning |
|---|---|
|
An optional integer — cells are either an integer or |
|
An optional set of integers — cells are either a set or |
|
A set of optional integers — set elements can be integers or |
|
An optional set of optional integers |
The Type Meta-Type
When an axis type is Type, the axis values are type names rather than term values. This is useful for permission matrices that map across all types in a specification:
permissions: Role, Type.
{CRUD}, admin, operator, customer
Pet, {_}, { read }, { read }
Owner, {_}, {}, {}
The X-axis holds Role tags (admin, operator, customer). The Y-axis holds type names (Pet, Owner). In generated code, both axes become string keys.
Validation Rules
Paradox enforces several compile-time checks on matrices.
Type Consistency
Every value must match the declared type for its position. If the X-axis type is Type, all X-axis values must be types. If the body type is Boolean, every cell must be a boolean.
Mixing types produces an error:
foo: Type, Text.
Boolean, Bar, "wrong"
"wat", true, false
[error 10]: Type mismatch in matrix declaration
foo: Type, Text.
----
indicates that the x-axis of the matrix should be Types
Boolean, Bar, "wrong"
-------
but this is a Text, which does not match.
Code Generation
Matrices generate nested maps keyed by the string representation of each axis value.
TypeScript
export type permMatrix = Record<string, Record<string, Set<MFoo>>>
export const perm: permMatrix = {
'Integer': {
'Text': new Set(['bar']),
'Boolean': new Set(['baz']),
'Unit': new Set(['bar'])
},
'Unit': {
'Text': new Set(['baz']),
'Boolean': new Set(['baz']),
'Unit': new Set(['bar'])
}
};
Haskell
perm :: Map Text (Map Text (Set.Set (MFoo)))
perm = Map.fromList
[ ("Integer", Map.fromList
[ ("Text", (Set.fromList [Bar]))
, ("Boolean", (Set.fromList [Baz]))
, ("Unit", (Set.fromList [Bar]))
])
, ("Unit", Map.fromList
[ ("Text", (Set.fromList [Baz]))
, ("Boolean", (Set.fromList [Baz]))
, ("Unit", (Set.fromList [Bar]))
])
]
The outer map is keyed by X-axis values, the inner map by Y-axis values. Both axes are rendered as their string representations. The structure is the same across all target languages — only the map and set constructors differ.