Types

Primitives

Paradox has six primitive types:

Type Description Literal Examples

Integer

Arbitrary-precision integers

0, 42, -7

Text

Unicode text strings

"hello", ""

Char

Single Unicode character

'a', 'Z'

Boolean

Truth values

true, false

Double

Double-precision floating point

3.14, -0.5

Unit

The unit type (empty value)

()

Product Types

Product types (records) are defined with type, followed by named fields:

type Pet
  name: Text
  age: Integer
  species: Species

Each field has a name and a type, indented under the type declaration.

Product Literals

Construct product values with the type name followed by field assignments:

Tuple:
  first: 42
  second: "hello"

Field Access

Access fields with dot notation:

pet.name
pet.age
tuple.first

Parametric Products

Products can be parameterized over type variables:

type Tuple a b
  first: a
  second: b

Type variables are lowercase identifiers after the type name.

Union Types

Union types (sum types / tagged unions) are defined with union, followed by named members:

union Species
  cat
  dog
  horse
  snake

Each member is a named variant of the union, indented under the union declaration. Members without payloads are simple enumerations. Members can also carry data:

union Either l r
  left: l
  right: r

Union Construction

Construct union values with TypeName.member:

Either.left 42
Either.right "hello"
Maybe.just x

For nullary members (no payload), the member is referenced by name:

Species.cat

Parametric Unions

Union types can be parameterized just like products:

union Maybe a
  just: a
  nothing

union These a b
  this: a
  that: b
  these: Tuple a b

Wrap Types

Wrap types create a new named type around an existing type, similar to Haskell’s newtype:

wrap Natural: Integer

valid Natural
  unwrap >= 0

Wraps are a zero-cost abstraction in backends that support it (e.g. Haskell’s newtype), meaning the wrapper is erased at runtime with no performance overhead. In other backends (e.g. TypeScript), wraps may compile to a thin runtime wrapper.

Wraps are commonly paired with validation to create refined subtypes.

Unwrap

Every wrap type has a built-in unwrap accessor, analogous to a field on a product or a member on a union. Access the inner value with dot notation:

myNatural.unwrap        // Natural -> Integer

For nested wraps, unwrap can be chained:

wrap Natural: Integer
wrap Positive: Natural

myPositive.unwrap           // Positive -> Natural
myPositive.unwrap.unwrap    // Positive -> Natural -> Integer

Each .unwrap peels off one layer of wrapping.

The unwrap accessor can also be used unqualified inside valid blocks, where the subject is implicit:

valid Positive
  unwrap as Integer != 0

Cast Chains

As an alternative to chaining .unwrap, use as to cast through multiple wrap layers in one step:

wrap Natural: Integer
wrap Positive: Natural

valid Positive
  unwrap as Integer != 0

Here Positive wraps Natural which wraps Integer. The expression unwrap as Integer casts through the chain from Positive down to Integer, equivalent to unwrap.unwrap.

For a full description of the casting system — including primitive casts, collection casts, and how the compiler resolves cast paths — see Casting.

Composite Types

Paradox has four composite type constructors:

Syntax Type Example Values

[T]

Array of T

[1, 2, 3], []

{T}

Set of T

{1, 2, 3}, {}

T?

Optional T (value or empty)

42, ()

T. U

Function from T to U

x. x + 1

Arrays

names: [Text]
  ["Alice", "Bob", "Carol"]

Sets

tags: {Text}
  {"admin", "user"}

Optionals

Optionals represent a value that may or may not be present. The empty optional is ():

type URIAuth
  user: Text?
  name: Text
  port: Port?

Functions

Function types use . (dot) as the arrow:

Integer. Integer        | a function from Integer to Integer
Integer. Integer. Integer  | curried binary function
(Integer. Integer). Integer  | higher-order: takes a function

Type Parameters and Polymorphism

Type parameters are lowercase identifiers:

type Tuple a b
  first: a
  second: b

identity: x:a. a
  x

Type variables are universally quantified. Paradox infers type parameters from usage in function signatures and type definitions.

Higher-kinded type variables are supported in interfaces:

interface for: f a. (a. b). f b

Here f is a type constructor (like [], Maybe, or Either l) and a is the element type.

See Also

  • Expressions — How to construct values and compute with types

  • Casting — The as operator, wrap graph resolution, and primitive casts

  • Validation — Refinement types constraining values

  • Pattern Matching — Destructuring union and composite types

  • URI Types — First-class URI literals with typed request/response pairs