Interfaces

Interfaces are Paradox’s mechanism for typeclass-style polymorphism. An interface declares a signature and a combiner function, then types provide instances.

Defining an Interface

An interface has a name, a type signature, and a combiner:

interface valid: a. Boolean
  x. y. x && y

This declares:

  • Name: valid

  • Signature: for any type a, produces a Boolean

  • Combiner: when a type has multiple instance expressions, combine them with &&

interface illuminate: a. Text
  x. y. x ++ y

The illuminate interface produces Text and combines multiple expressions with ++ (concatenation).

Built-in Interfaces

Paradox’s standard library defines three interfaces:

valid

interface valid: a. Boolean
  x. y. x && y

Constrains the set of valid values. Multiple validation expressions are combined with logical AND.

illuminate

interface illuminate: a. Text
  x. y. x ++ y

Produces human-readable display text. Multiple illumination expressions are combined with concatenation.

for

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

Maps a function over a type constructor. This is higher-kinded: f is a type constructor (like [] or Maybe), and a is the element type. Built-in support exists for arrays, sets, and optionals — no import needed.

Instances for Products

Product instances provide multiple expressions that are combined:

type Pet
  name: Text
  age: Integer

valid Pet
  name != "" | Pet's gotta have a name
  age > 0

Each expression in the valid Pet block is combined using the interface’s combiner (&&), producing: (name != "") && (age > 0).

Instances for Unions

Union instances use case patterns:

union Status
  approved: Integer
  rejected
  pending

valid Status
  approved: amount. amount > 0
  rejected: true
  pending: true

Each tag gets its own expression. Tags with payloads bind a variable.

User-Defined Interfaces

Define your own interfaces beyond valid and illuminate:

interface serialize: a. Text
  x. y. x ++ y

type Person
  name: Text
  age: Integer

serialize Person
  name
  " is "
  age as Text
  " years old"

Multiple instance expressions are combined with the interface’s combiner. Here, four text expressions are concatenated.

Use the interface as a regular function:

result: Text
  serialize aPerson

Higher-Kinded Interfaces

The for interface demonstrates higher-kinded type variables:

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

Instances specialize f to a particular type constructor:

for (Either l)
  left: x. f. Either.left x
  right: x. f. Either.right (f x)

for Maybe
  just: x. f. Maybe.just (f x)
  nothing: f. Maybe.nothing

for (Tuple a)
  f. Tuple:
    first: first
    second: f second

For Either l, only the right branch applies the function — the left value passes through unchanged. This is the standard functor behavior.

Resolution

Interface calls are resolved through normal function application at type-check time. When you write for xs (f), it is parsed as a normal function application: Apply (Apply (Term "for") xs) (Lambda …​). The type checker resolves which instance to use based on the type of the argument.

Interfaces are not special AST constructors. They are normal functions that are dispatched through the instance map at type-check time.

See Also

  • Validation — The valid interface in depth

  • Illumination — The illuminate interface in depth

  • Collection — The for interface instances

  • Algebra — for instances on algebraic types