Skip to content
Noodle
InstallLearnPlayground
GitHub

Basic types

Noodle’s basic value types cover completion, truth values, integers, floating-point numbers, Unicode code points, and text. They are distinct static types: Noodle never silently converts one into another.

The default standard library’s Prelude makes their conventional source names available. Prelude and modules explains that relationship in detail.

Unit has one value, written (). It represents a computation that completes without returning meaningful data:

func announce() -> Unit do
Console.println("ready");
end

A block with no tail expression also produces Unit, so effect-only functions normally end with a semicolon-terminated statement rather than an explicit ().

Bool has the literals true and false. Conditions require an actual Bool; numbers, strings, and other values are not treated as truthy:

func access_label(allowed : Bool) -> String do
if allowed then "allowed" else "denied" end
end

Boolean operators are ! for negation, && for conjunction, and || for disjunction. && and || short-circuit, so the right side is evaluated only when needed.

Int is a signed 32-bit integer. Decimal, hexadecimal, and binary literals all produce Int values:

func main() -> Unit do
decimal = 42;
hexadecimal = 0x2a;
binary = 0b101010;
Debug.trace(decimal == hexadecimal && hexadecimal == binary);
end

Arithmetic operators include +, -, *, /, and %. Integer division truncates toward zero. Noodle defines the result of overflow and division by zero; these operations do not raise an arithmetic runtime error.

Int values range from -2147483648 through 2147483647. Addition, subtraction, multiplication, and unary negation use wrapping arithmetic. The mathematical result is reduced modulo 2^32, then the resulting 32 bits are interpreted as a signed two’s-complement value.

func main() -> Unit do
Debug.trace(2147483647 + 1);
Debug.trace(-2147483648 - 1);
Debug.trace(50000 * 50000);
Debug.trace(-(-2147483648));
end

The four results are, in order:

-2147483648
2147483647
-1794967296
-2147483648

Wrapping applies to a value produced by an operation. It does not make every integer literal valid: 2147483648 is rejected at compile time. Its magnitude is accepted only as the direct operand of -, which allows the minimum value -2147483648 to be written.

For a nonzero divisor, / performs signed integer division and discards the fractional part toward zero. % returns the corresponding remainder. A nonzero remainder has the sign of the dividend, the left operand:

func main() -> Unit do
Debug.trace(7 / 3);
Debug.trace(-7 / 3);
Debug.trace(-7 % 3);
Debug.trace(7 % -3);
end

These expressions produce 2, -2, -1, and 1. For every nonzero right, division and remainder satisfy:

left == (left / right) * right + (left % right)

When the divisor is zero, both operations evaluate to 0:

func main() -> Unit do
Debug.trace(42 / 0);
Debug.trace(42 % 0);
Debug.trace(0 / 0);
end

All three results are 0. No error is thrown, and the result does not retain enough information to distinguish division by zero from an ordinary zero result. Check the divisor before the operation when zero is invalid for the application, and represent that case explicitly with a type such as Option or Result.

There is one overflowing division boundary: -2147483648 / -1 wraps to -2147483648; the corresponding remainder is 0.

Comparisons use ==, !=, <, <=, >, and >= and produce Bool.

Double uses binary64 floating-point values. A decimal point or exponent distinguishes a Double literal:

func circle_area(radius : Double) -> Double do
3.141592653589793 * radius * radius
end

Int and Double are different types. Write an explicit conversion such as value.to_double() when one is required. Lossy operations such as floor_to_int() and truncate_to_int() state their rounding behavior in the method name.

More numeric helpers are listed in Strings and characters, alongside the standard conversion methods.

Char represents one Unicode code point and uses single quotes:

func main() -> Unit do
latin = 'N';
emoji = '😀';
Debug.trace(latin);
Debug.trace(emoji);
end

A character is not an integer or a one-character string. The standard library provides explicit to_int() and to_string() conversions, plus Char.from_codepoint. See Strings and characters.

String is immutable Unicode text and uses double quotes. ++ concatenates two strings:

func greeting(name : String) -> String do
"Hello, " ++ name ++ "!"
end

Use a raw string when text contains quotes, backslashes, comment markers, or several physical lines that should be copied without escape processing:

func message() -> String do
#|first line
#|second line with "quotes" and \\slashes
#|
end

The #| prefix is structural and is removed from each line. Consecutive prefixed lines are joined with \n; the final empty #| line requests a trailing newline. Raw strings can appear anywhere a string literal is accepted, including test names and attribute arguments.

Strings do not expose integer indexing or a code-point length() operation. The standard library uses Unicode-aware cursors and provides joining, iteration, slicing, and conversion APIs in Strings and characters.

An annotation checks a type; it does not request conversion:

func main() -> Unit do
count : Int = 42;
ratio : Double = count.to_double();
Console.println(ratio.to_string());
end

The available conversion method depends on the source type’s associated standard-library extensions. Conversion is kept visible at the call site.

These basic types are enough to understand bindings and function boundaries. Other important types are introduced where their behavior becomes useful:

Next: values and bindings.