Types
The Nodora type system
Nodora is statically typed. Every expression has a type that the compiler checks before any code runs. Type errors are reported with a source location at compile time.
Primitive types
| Type | Literal examples | Notes |
|---|---|---|
string | "hello", "" | Double-quoted. Supports \n, \t, \r, \", \\. |
number | 0, 42, 3.14 | Stored as 64-bit float internally. |
bool | true, false | |
object | { a: 1, "b": "x" } | String keys, heterogeneous values. |
array | [1, 2, 3], ["a", "b"] | Written as array<T> in built-in signatures. |
There is no separate integer type — 1 and 1.0 are the same value.
The built-in int(x) truncates a number to its integer part.
Compound types
Arrays
Arrays are homogeneous in signatures but can hold mixed values at
runtime. Built-in argument types are written array<T> (for example
array<string> or array<any>).
xs = [1, 2, 3]
first = xs[0] // 1
flat = arrays::flatten([[1, 2], [3]])Indexing an array out of bounds is an evaluation error. Indexing with a non-integer is also an error.
Objects
Objects are unordered string-keyed maps. Keys can be bare identifiers or string literals; values are any expression.
user = {
name: "Alice",
"age": 30,
tags: ["admin", "ops"]
}
n = user.name // dot access
a = user["age"] // bracket access
t = user.tags[0] // chainedAccessing a missing key yields undefined.
undefined
undefined is a distinct value that represents absence. It is what
you get from:
- A missing object field (
input.nope). - A division or modulo by zero.
- Any operation whose operand is itself
undefined(propagation).
Outputs whose value is undefined are omitted from the evaluation
result rather than serialized as null. This makes it safe to write
optional outputs without guard expressions.
any
any is the top type. Argument slots typed any accept values of any
type, and any is assignable from anything. You will mostly see it in
built-in signatures like is_defined(value: any) -> bool.
Union types
Some built-ins accept a union of types. Unions are written A|B|... in
signatures, for example:
len(value: union<string|array<any>|object>) -> number
min(value: union<array<number>|array<string>>) -> union<number|string>A value is assignable to a union if it is assignable to at least one member.
Lambda types
Higher-order built-ins (some, every,
arrays::find_index, arrays::group_by) take a lambda. Lambdas are
written with pipes:
positive = |x| x > 0
has_pos = some(input.numbers, |x| x > 0)
idx = arrays::find_index(input.items, |it| it.flagged)Lambda types are written |T1, T2, ...| -> R.
Lambdas are first-class — you can bind one to a name and pass it later — but they cannot be stored in JSON inputs or outputs. They only exist during a single evaluation.
Type checking and coercion
Nodora does not implicitly coerce between types. Comparing a
string to a number is a type error; concatenating a number to a
string with + is a type error. Use sprintf or conv::atoi /
conv::atof for explicit conversion.
Undefined propagation rules
| Situation | Result |
|---|---|
Any operand of + - * / % is undefined | undefined |
Any operand of < <= > >= is undefined | undefined |
== or != with an undefined operand | undefined |
&& / || with an undefined operand | undefined |
a.b where a is undefined | undefined |
arr[i] where arr or i is undefined | undefined |
Division or modulo by 0 | undefined |
Most built-ins receiving undefined | undefined |
Use is_defined(x) to test explicitly.