Default: Typeclass and Defaulting
In Rex, “default” can mean two different things:
- The
Defaulttypeclass methoddefault : a - The defaulting pass that resolves ambiguous type variables for defaultable classes
1) Default Typeclass (default : a)
The prelude provides Default and a set of built-in instances.
Built-in types with Default:
Boolu8,u16,u32,u64i8,i16,i32,i64f32,f64StringList aOption aResult a e(whenDefault ais available)
Implementing Default for custom ADTs
You can implement Default for many ADT shapes.
Single constructor with unnamed fields:
type Pair = Pair i32 Bool;
instance Default Pair where {
default = Pair 42 true;
}
Single constructor with named fields:
type Config = Config { retries: i32, enabled: Bool };
instance Default Config where {
default = Config { retries = 3, enabled = false };
}
Multiple variants (enum) with no fields:
type Mode = Fast | Safe | Debug;
instance Default Mode where {
default = Safe;
}
Multiple variants with mixed payload shapes:
type Token = Eof | IntLit i32 | Meta { line: i32, col: i32 };
instance Default Token where {
default = Meta { line = 1, col = 1 };
}
Generic ADTs with constraints:
type Box a = Box a | Missing;
instance<a> Default (Box a) <= Default a where {
default = Box default;
}
Constructing named options with selected overrides
For a single-constructor ADT with named fields, the constructor itself can request the type’s default and override only the fields you supply:
type Config = Config { retries: i32, enabled: Bool, label: String };
instance Default Config where {
default = Config { retries = 3, enabled = false, label = "standard" };
}
Config { retries = 9 }
This evaluates to a Config with retries = 9, enabled = false, and label = "standard".
Use Config {} when you want the default value while keeping its type explicit.
Omitted fields come from Default Config as a whole. Rex does not default each field separately,
so manually chosen and interdependent defaults are preserved. Complete construction still works
without a Default instance, while partial construction requires one.
This shorthand is intentionally limited to single-variant record ADTs. For an ADT with multiple variants, a type-level default might produce a different variant from the constructor you named.
Ambiguous default calls and is
When multiple Default instances are in scope, default may be ambiguous until you pin the type.
Record updates require a definitely known base type.
Failing example:
type A = A { x: i32, y: i32 };
type B = B { x: i32, y: i32 };
instance Default A where {
default = A { x = 1, y = 2 };
}
instance Default B where {
default = B { x = 10, y = 20 };
}
{ default with { x = 9 } }
In the editor/playground, use the quick fix on this error to insert is for the intended ADT.
Try it on the example above.
Passing example (same setup, with explicit is):
type A = A { x: i32, y: i32 };
type B = B { x: i32, y: i32 };
instance Default A where {
default = A { x = 1, y = 2 };
}
instance Default B where {
default = B { x = 10, y = 20 };
}
{ (default is A) with { x = 9 } }
Another failing example (same ambiguity in a let binding):
type A = A { x: i32, y: i32 };
type B = B { x: i32, y: i32 };
instance Default A where {
default = A { x = 1, y = 2 };
}
instance Default B where {
default = B { x = 10, y = 20 };
}
let
a = { default with { x = 9 } },
b = { default with { y = 8 } }
in
(a, b)
For this let-binding form, quick fixes offer two styles: add is to the
default call, or add a type annotation on the binding (for example
a: A = ...). These same quick fixes are also exposed through LSP code
actions and can be used by LLM-driven tooling.
2) Type Defaulting (Ambiguous Types)
Some overloaded prelude operations (such as zero) only require class constraints:
zero
If nothing else forces a concrete type, Rex runs defaulting to pick a concrete type from the defaulting candidates that satisfy the required class constraints.
If you see an “ambiguous overload” error around numeric expressions, force a type:
let z: i32 = zero in z
Or use the value in a way that constrains it:
zero + 1
Integer literals are also overloaded (over Integral) and become concrete from context:
let
x = 4,
f: u16 -> u16 = \n -> n
in
f x
Negative literals must resolve to a signed type:
let
x: i32 = -3,
f: i32 -> i32 = \n -> n
in
f x
let x: u32 = -3 in x is a type error.
The defaulting algorithm is specified in Specification (“Defaulting”).