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

Integer?

An optional integer — cells are either an integer or ()

{Integer}?

An optional set of integers — cells are either a set or ()

{Integer?}

A set of optional integers — set elements can be integers or ()

{Integer?}?

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.

No Duplicate Axis Values

Each value in the X-axis must be unique, and each value in the Y-axis must be unique:

foo: Bar, Baz.
  Boolean, wat, wat, frog, frog
  zip,     true, false, false, false
  zam,     false, true, true, true
[error 11]: Duplicate found in X axis of the foo Matrix

Refinement Constraints

Values in matrix cells must satisfy any refinement constraints on their types. If a body cell contains a value of a validated type, the constraint is checked at compile time just like any other expression.

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.