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 aBoolean -
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.
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
validinterface in depth -
Illumination — The
illuminateinterface in depth -
Collection — The
forinterface instances -
Algebra —
forinstances on algebraic types