Casting

Paradox uses the as operator to convert values between compatible types. The casting system is built around a wrap graph — a bidirectional graph of wrap type relationships that the compiler uses to find the shortest conversion path between any two types.

The as Operator

Cast a value to a compatible type with as:

age as Text
count as Double
flag as Integer

as has the lowest operator precedence, so it binds last:

x + 1 as Text       // equivalent to: (x + 1) as Text

Primitive Casts

Certain primitive-to-primitive casts are built in:

From To

Any primitive

Text

Integer

Double

Integer

Boolean

Boolean

Integer

Text

Boolean

Text

Integer?

Type

Text

These are direct conversions that don’t go through the wrap graph.

Collection Casts

Arrays and sets with the same element type are interchangeable:

myArray as {Text}    // [Text] -> {Text}
mySet as [Integer]   // {Integer} -> [Integer]

Collection casts deduplicate when converting to a set, and produce an unspecified order when converting from a set to an array.

Wrap Casts

Wrap types form the core of the casting system. Each wrap declaration creates a bidirectional edge in the wrap graph:

wrap Natural: Integer
wrap Positive: Natural
wrap Port: Natural

This produces a graph:

Integer <-> Natural <-> Positive
                    <-> Port

The compiler uses shortest-path search (Dijkstra’s algorithm) to find a cast route between any two types connected in this graph. The search prefers the shortest distance, favouring zero-cost operations (data access like unwrap) over computational conversions (like primitive casts). This means a path through wrap/unwrap edges — which are free at runtime in backends that support it — will always be chosen over a longer path that involves computation.

myPositive as Integer    // Positive -> Natural -> Integer (2 unwraps, zero-cost)
myPositive as Port       // Positive -> Natural -> Port (1 unwrap + 1 wrap, zero-cost)
myNatural as Port        // Natural -> Port (1 wrap, zero-cost)
myPort as Integer        // Port -> Natural -> Integer (2 unwraps, zero-cost)

Each step in the path is either a wrap (constructing a wrapper) or an unwrap (extracting the inner value). Both are data access operations — zero-cost where the backend supports it. The compiler only falls back to computational primitive casts when no wrap-only path exists.

Unwrap-then-Primitive Casts

When a wrap type bottoms out at a primitive, the compiler can chain an unwrap path with a primitive cast:

wrap Name: Text
wrap UserId: Integer

myUserId as Text         // UserId -> Integer -> Text (unwrap + primitive cast)
myName as Boolean        // Name -> Text -> Boolean (unwrap + primitive cast)

The compiler first unwraps to the innermost primitive, then applies a built-in primitive cast if one exists.

Cast Errors

When no path exists between two types, the compiler reports the unwrap chains of both types to help diagnose the problem:

Cannot cast from Foo to Bar automatically
  Foo unwraps to: Foo -> Integer
  Bar unwraps to: Bar -> Text

Identity Casts

Casting a value to its own type is always valid and is a no-op:

x as Integer             // no-op when x is already Integer

See Also