Expressions
Paradox has a pure functional expression language. Every expression evaluates to a value — there are no statements or side effects.
Literals
Unit Literal
()
The unit value represents "nothing" and is the sole inhabitant of the Unit type. It also serves as the "none" case for optionals.
URI Literals
URIs have dedicated literal syntax. Any scheme:// prefix is parsed as a structured URI value with optional path, query, fragment, HTTP methods, MIME types, and request/response types:
https://api.example.com/v1/users GET (Unit. UserList)
http://localhost:8080/health
URI literals support ${} interpolation in every component. See URI Types for full syntax.
String Interpolation
Embed expressions inside text strings with ${}:
"Hello, ${name}!"
"Age: ${age as Text}"
"Total: ${x + y}"
Interpolated expressions are automatically cast to Text when possible.
Heredocs
Multi-line text literals use triple quotes, wrapped in parentheses:
("""Hello ${name}!
Welcome to Paradox.""")
Heredocs support interpolation and escape sequences just like regular strings:
("""Line 1\nName: ${name}\tValue: ${value}\nLine 3""")
Product Literals
Construct product values by listing field assignments indented under the type name:
Tuple:
first: 42
second: "hello"
Pet:
name: "Rex"
age: 5
species: Species.dog
When a top-level definition’s return type matches the product being constructed, the type name can be omitted. The fields are written directly under the definition:
type Address
street: Text
country: Country
// Explicit form — type name required:
mkAddress: country: Country. street: Text. Address
Address:
street: street
country: country
// Sugar form — type name omitted because return type is Address:
mkAddress: country: Country. street: Text. Address
street: street
country: country
The sugar form is equivalent to the explicit form. The compiler infers the product constructor from the declared return type.
Union Construction
Construct union values with TypeName.tag:
Either.left 42
Either.right "error"
Species.cat
Maybe.just x
Nullary tags (no payload) are referenced directly. Tags with payloads take an argument.
Lambda Expressions
Anonymous functions use the . (dot) syntax:
x. x + 1
x. y. x + y
name. "Hello, " ++ name
The parameter is to the left of the dot, the body to the right. Multi-argument functions are curried:
x. y. z. x + y + z
If-Then-Else
Conditional expressions require both branches:
if x > 0 then "positive" else "non-positive"
Both then and else branches must have the same type. There is no if without else — every conditional must produce a value.
Chain for multiple conditions:
grade: score:Integer. Text
if score >= 90 then "A"
else if score >= 80 then "B"
else if score >= 70 then "C"
else "F"
Use inline in concatenation:
"The number is " ++ (if n > 0 then "positive" else "non-positive")
Let Bindings
Bind intermediate results with let … in. Every let must be paired with an in that introduces the body expression.
Type Annotations
A binding can include an explicit type annotation between the name and the value. The syntax is name: Type: value:
let x: Integer: 5 in x + 1
let sum: Integer: 10 + 20 in sum * 2
Type annotations are checked at compile time but have no runtime cost.
Block Form
Multiple bindings use a block form. Each binding is indented under let, and in introduces the body:
let
x: Integer: a + 1
y: Integer: b + 2
in x + y
Bindings are evaluated sequentially — each binding can reference all previous bindings:
let
typed: Integer: n * 2
untyped: typed + 1
in untyped
Type annotations can be mixed freely with untyped bindings in the same block.
Scoping
Each binding is in scope for all subsequent bindings and the body expression. Bindings from an outer let can be shadowed by an inner let:
let x: 1 in let x: x + 1 in x // evaluates to 2
However, duplicate names within the same block are a parse error:
// ERROR: duplicate binding
let
x: 1
x: 2
in x
Chained Let Expressions
Let expressions can be nested to build up a sequence of computations:
let x: n + 1 in let y: x * 2 in y
This is equivalent to the block form:
let
x: n + 1
y: x * 2
in y
Type Variables
When a let binding appears inside a polymorphic function, the type annotation can reference type variables from the enclosing function signature:
myIdentity: a: a. a
let x: a: a in x
Let in Match Branches
Let bindings can appear inside match case bodies:
colorValue: c: Color. Integer
match c:
red: let val: 255 in val
green: let val: 128 in val
blue: let val: 64 in val
A let can also bind the result of a match:
processColor: c: Color. Integer
let base: match c:
red: 100
green: 200
blue: 300
in base + 50
Let in Validation Rules
Let bindings can appear in valid blocks to name intermediate values used in conditions:
valid PersonWithLet
let minAge: 0 in age >= minAge | Age must be non-negative
let maxAge: 150 in age <= maxAge | Age must be reasonable
valid RangeWithLet
let diff: max - min in diff >= 0 | Max must be >= min
Function Application
Apply a function to an argument by juxtaposition:
f x
add 1 2
reduce xs add 0
Paradox is not a curried language. Multi-argument functions take their arguments positionally, not via partial application. f x y applies f to two arguments x and y — it is not (f x) y where f x returns a new function. All arguments are passed directly at the call site.
Operators
Comparison
| Operator | Description |
|---|---|
|
Equality |
|
Inequality |
|
Greater than |
|
Less than |
|
Greater than or equal |
|
Less than or equal |
Note: = is equality comparison, not assignment.
Type Casting
Cast between compatible types with as:
age as Text
unwrap as Integer
x as Natural
Cast chains follow the wrap hierarchy. For example, if Positive wraps Natural which wraps Integer, then unwrap as Integer casts through the full chain.
For the full casting reference — including primitive casts, wrap graph resolution, and collection casts — see Casting.
Optional Default
The ? operator provides a default value for optionals:
name? "anonymous"
port? 8080
If the optional has a value, it is returned; otherwise, the default is used.
Operator Precedence
From highest to lowest binding:
-
Field access (
.) -
Function application (juxtaposition)
-
Unary
not -
*,/,% -
+,- -
++ -
=,!=,>,<,>=,<= -
&& -
|| -
→ -
as
See Also
-
Types — The type system underlying expressions
-
Functions — Defining named functions
-
Pattern Matching — Destructuring values
-
URI Types — Structured URI literals and API specification