Static copy
Copied from docs/en/manual/syntax-and-types.md in https://github.com/poppy-team/aipo-lang (MIT). Pinned to revision ce47c4e070404fc41722ccd8e2b7b808b99062a8, blob 43b340df1250fead2a570ea72d20f3f87df59ba9. The source repository remains canonical; this copy is refreshed through a sync pull request, not live.
Aipo was engineered to offer lean, deterministic syntax free from dangerous implicit behaviors. This section details the type system, canonical best practices for writing idiomatic code, and modern language features.
To guarantee clean, high-performance, and accessible code (especially welcoming for developers with ADHD and dyslexia), Aipo establishes clear canonical rules:
| Canonical Practice (The Aipo Way) | Anti-Pattern to Avoid | Why this matters |
|---|---|---|
let name = "Dev" (Immutable by default) | var name = "Dev" without need for mutation | Prevents accidental mutation and simplifies deterministic reasoning about state. |
struct Point { x, var y = 0 } (Immutable fields by default) | Global mutations or unstructured state | Guarantees struct integrity; mutation requires explicit intent via var. |
User{ name: "Ana", age: 28 } (Key-value with :) | Divergent mapping syntax | Colon unifies key-value association across dictionaries and structs without motor confusion. |
if condition { ... } (Brace blocks) | Redundant parentheses around conditions | Lean syntax without parenthesis clutter, fully compatible with native rainbow brackets and code folding. |
Type:deposit(var self, amount) (Explicit mutator receiver) | Hidden side effects in methods | var is the universal keyword for mutability, making side effects visually clear. |
a // b and a //= b (Integer division) | Truncated manual casts | Expressive and symmetric canonical arithmetic operator alongside +, -, *, /. |
f"User {id}: {email}" (Direct interpolation) | String concatenation "User " + String(id) + ": " + email | Direct interpolation eliminates visual clutter and intermediate heap allocations. |
r"C:\data\report.csv" (Raw strings) | Escape sequences "C:\\data\\report.csv" | Raw strings eliminate backslash pollution in file paths and regex patterns. |
let city = user?.profile?.city (Safe navigation) | Defensive cascading if user != none and ... | Safe navigation avoids redundant nested null-checking boilerplate. |
let port = load_port() or_else 8080 (Fallback or_else) | Heavy error handling attempt { port = ... } failed ... | or_else provides immediate default values in failure-prone expressions. |
| `data | > filter() | > calculate()(Pipeline|>`) |
list.add(item) (Universal insertion) | Heterogeneous collection operations | .add() is the universal canonical insertion method for lists and sets. |
data.lazy().filter(...).collect() (Lazy evaluation) | Intermediate heap allocations | .lazy() consumes constant memory without allocating temporary intermediate lists. |
Text handling in Aipo is robust, expressive, and mathematically predictable. Every string is guaranteed to be valid UTF-8 with automatic Unicode NFC canonical normalization.
f"...") Interpolation with the f prefix is the canonical way to format messages and compose text:
let user = "Alice"
let score = 98.5
let level = 4
# Interpolation with variables and numeric expressions
let report = f"Player: {user} | Level: {level} | Points: {score}"
io.println(report)
# Prints: "Player: Alice | Level: 4 | Points: 98.5"
# Executing operations directly inside interpolated braces
let delta = 1.5
io.println(f"Next milestone: {score + delta}") # 100.0If you need to include literal { or } characters inside an f-string, duplicate them ({{ and }}):
let key = "token"
let value = "xyz123"
# Renders a JSON object with literal braces and interpolated values
let json_payload = f"{{\"{key}\": \"{value}\"}}"
io.println(json_payload) # {"token": "xyz123"}r"...") In conventional languages, writing host file paths on Windows or regular expressions requires doubling every backslash (\\\\), generating visual noise that impairs readability.
With Aipo's Raw Strings (r"..."), backslashes are preserved literally:
# 1. Host system file paths
let windows_path = r"C:\Users\dev\AppData\Local\Aipo\config.toml"
io.println(windows_path)
# Prints literally: C:\Users\dev\AppData\Local\Aipo\config.toml
# 2. Clean regular expression patterns without double backslashes
let email_pattern = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
io.println(email_pattern)"""...""" and r"""...""") For long blocks of text, inline documentation, SQL queries, or templates, use triple quotes. They preserve formatting, indentation, and line breaks with clarity:
let sql_query = """
SELECT u.id, u.name, p.role
FROM users u
JOIN permissions p ON p.user_id = u.id
WHERE u.active = true
ORDER BY u.name ASC
"""
io.println(sql_query)fr"..." / rf"...") When you need to compose a dynamic regex pattern or a system path containing interpolated variables without escaping backslashes, combine both prefixes:
let folder = "logs"
let extension = "txt"
# Combines raw string semantics (unescaped backslashes) with variable interpolation:
let dynamic_path = fr"C:\system\{folder}\app.{extension}"
io.println(dynamic_path)
# Prints: C:\system\logs\app.txt.encode() and .decode()) For networking and binary file I/O, strings and Bytes buffers convert directly without third-party dependencies:
let message = "Deterministic Aipo"
# Converts UTF-8 String into a raw Bytes buffer
let raw_bytes = message.encode()
io.println(f"Byte size: {raw_bytes.len()}")
# Decodes Bytes buffer back into a normalized UTF-8 NFC String
let original_text = raw_bytes.decode()
io.println(original_text) # "Deterministic Aipo"Aipo adopts the immutable-by-default principle consistently across all scopes:
# Immutable: compiler prevents subsequent reassignments
let service_fee = 0.15
# Mutable: reserved for loop accumulators or local state
var accumulated_total = 100.0
accumulated_total += 25.0
io.println(accumulated_total) # 125.0Golden Rule
Always start new variable declarations with let. Only change to var if the variable is explicitly mutated in the local execution flow.
Int) Signed 64-bit integers (i64). Supports base prefixes and visual grouping with underscores (_):
let decimal = 42
let million = 1_000_000 # Visual separator for effortless readability
let hexadecimal = 0xFF # 255
let binary = 0b101010 # 42
let octal = 0o777 # 511Float) 64-bit IEEE 754 double-precision numbers:
let pi = 3.14159
let fractional = 0.005/) vs Truncated Integer Division (//) In Aipo, the / operator always returns a Float. To perform truncated integer division, use the symmetric // operator:
let a = 10
let b = 3
let real_div = a / b # 3.3333333333333335 (Float)
let integer_div = a // b # 3 (Int)
# Symmetric compound assignment
var value = 20
value //= 3
io.println(value) # 6No Silent NaN or Infinity
Aipo's value model strictly forbids corrupted values like NaN or Infinity. Invalid mathematical operations (such as division by zero or square roots of negative numbers) trigger structured runtime failures immediately.
List) Ordered dynamic arrays indexed from 0.
.add(item) (Aipo standardizes on .add across lists and sets).[-1] accesses the last element).let languages = [
"Rust",
"Aipo",
"TypeScript",
]
languages.add("Odin")
io.println(languages[0]) # "Rust"
io.println(languages[-1]) # "Odin"
io.println(languages.len()) # 4Dict) Associative key-value maps declared with {} that strictly preserve original insertion order:
let config = {
"server": "api.aipo.dev",
"port": 8443,
"ssl": true,
}
# Explicit presence verification with .has()
if config.has("port") {
let port = config["port"]
io.println(f"Configured port: {port}")
}Why is there no dict.get() method?
Aipo intentionally omits get(key) to prevent the classic ambiguity where a none return value could mean either that the key does not exist or that the key exists with an explicit value of none. In Aipo, use dict.has(key) to check existence and dict[key] to retrieve the value.
Set) Store unique elements while preserving the order in which they were first added:
let tags = Set(["backend", "compiler", "backend"]) # Duplicate discarded
tags.add("cli")
io.println(tags.has("backend")) # true
io.println(tags.len()) # 3
io.println(tags.to_list()) # ["backend", "compiler", "cli"]Sequence) To process large datasets without allocating intermediate collections, create a lazy sequence with .lazy():
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Chained operations evaluate on-demand in constant memory:
let tripled_evens = numbers.lazy()
.filter(x => x % 2 == 0)
.map(x => x * 3)
.take(2)
.collect()
io.println(tripled_evens) # [6, 12]enum) enum defines a closed sum type with compiler-verified variants, supporting unit variants, positional payloads, and named field payloads:
enum ExecutionMode {
Debug,
Optimized { level: Int },
Profiled(output_file: String),
}
let mode = ExecutionMode.Optimized { level: 3 }?.) Avoids nested null checks. If any link in the chain is none, the entire expression resolves to none without throwing a runtime error:
struct Address {
city
}
struct User {
address
}
# Symmetric instantiation using consistent colon (:) syntax:
let u1 = User{ address: Address{ city: "Curitiba" } }
let u2 = User{ address: none }
io.println(u1?.address?.city) # "Curitiba"
io.println(u2?.address?.city) # noneor_else) Provides an immediate fallback value if an expression produces a fail, without needing an explicit attempt block:
fn load_port(env) {
if env == "production" {
return 443
}
return fail("unknown environment")
}
# If load_port() produces a fail, or_else evaluates the right-hand fallback:
let port = load_port("test") or_else 8080
io.println(f"Active port: {port}") # 8080|>) Allows data transformations to be organized in a natural left-to-right sequence:
fn clean(txt) {
return txt.trim()
}
fn highlight(txt, prefix) {
return prefix + txt
}
# " alert " is passed as first argument to clean(), and result to highlight():
let label = " alert " |> clean() |> highlight("[URGENTE] ")
io.println(label) # "[URGENTE] alert"is) Checks whether a value matches a concrete language type:
let value = 42
if value is Int {
io.println("It is a safe 64-bit integer")
}
# To check for absence of value, check equality with none directly:
let data = none
if data == none {
io.println("Value is null")
}