Expressions

Paradox has a pure functional expression language. Every expression evaluates to a value — there are no statements or side effects.

Literals

Integer Literals

42
-7
0

Text Literals

"hello"
""
"with \"escaped\" quotes"

Character Literals

'a'
'Z'

Boolean Literals

true
false

Double Literals

3.14
-0.5

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.

Array Literals

[1, 2, 3]
["alice", "bob"]
[]

Set Literals

{1, 2, 3}
{"admin", "user"}
{}

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.

Single Binding

let doubled: x * 2 in doubled + 1

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.

Field Access

Access product fields with dot notation:

pet.name
pet.age
user.address.country

Operators

Arithmetic

Operator Description

+

Addition

-

Subtraction

*

Multiplication

/

Division

%

Modulo

Comparison

Operator Description

=

Equality

!=

Inequality

>

Greater than

<

Less than

>=

Greater than or equal

<=

Less than or equal

Note: = is equality comparison, not assignment.

Logical

Operator Description

&&

Logical AND

||

Logical OR

not

Logical NOT (prefix)

->

Logical implication

a -> b means "if a is true, then b must be true." Equivalent to not a || b.

Concatenation

Operator Description

++

Concatenate text, arrays, or sets

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:

  1. Field access (.)

  2. Function application (juxtaposition)

  3. Unary not

  4. *, /, %

  5. +, -

  6. ++

  7. =, !=, >, <, >=, <=

  8. &&

  9. ||

  10. 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