"Not a language for writing freely, but a language for converging correctly."
The essence of language design for LLMs is not maximizing expressiveness, but minimizing the set of valid candidates at each generation step.
| Principle | Definition |
|---|---|
| Predictable | The "next valid syntax, API, and semantics" can be narrowed down tightly at each generation step |
| Local | The information needed to understand or modify a given location is as close as possible |
| Repairable | When errors occur, the compiler returns near-unique fix candidates in few steps |
| Compact | High semantic density with low syntactic noise. Strict yet concise |
- Canonicity -- There should be, in principle, only one primary way to express the same meaning
- Surface Semantics -- Side effects, fallibility, optionality, and mutability must appear in the syntax or type
- Local Reasoning -- The meaning of a function or expression should be largely understandable from nearby syntax alone
- Incremental Completion -- Incomplete code is legal; one can make progress by filling typed holes
- Repair-First -- The compiler should be a repair tool, not a rejection tool; diagnostics are structured
- Vocabulary Economy -- The standard library has a consistent vocabulary with no synonyms
- No Magic -- Mechanisms that change meaning at runtime, context-dependent DSLs, and implicit type conversions are prohibited
- Writing freedom for experts
- Cultural "language feel" and ergonomic DSLs
- Metaprogramming power (no macros, no reflection)
- Operator overloading and implicit conversions
- Multiple return styles and lambda notations
- Ad-hoc polymorphism via implicit instance resolution
Goal: High conciseness + Low freedom
Identifier ::= [a-z_][a-zA-Z0-9_]*
A single trailing ? is allowed for predicates:
Name ::= Identifier | Identifier "?"
name?-- Bool predicate only (return type must beBool; compiler enforced)
TypeName ::= [A-Z][a-zA-Z0-9]*
IntLiteral ::= [0-9]+ | "0x" [0-9a-fA-F]+ // decimal or hex
FloatLiteral ::= [0-9]+ "." [0-9]+ ([eE] [+-]? [0-9]+)?
StringLiteral ::= '"' (char | "\n" | "\t" | "\r" | "\\" | "\"" | "\$")* '"'
InterpolatedStr ::= '"' (char | "${" Expr "}")* '"'
SingleQuoteStr ::= "'" (char | "\'" | "\\" | "\n" | "\t" | "\r")* "'" // escapes only, no interpolation
HeredocStr ::= '"""' ... '"""' // multiline, indent-stripped
RawHeredocStr ::= 'r"""' ... '"""' // no escapes, no interpolation
RawStr ::= 'r"' ... '"' // no escapes, no interpolation
BoolLiteral ::= "true" | "false"
There is no null literal. Absence is represented by none (a constructor of Option[T]).
Double-quote strings support interpolation via ${expr} and backslash escapes. Single-quote strings support escape sequences (\', \\, \n, \t, \r) but no interpolation. Raw strings and raw heredocs support neither interpolation nor escapes.
Heredoc strings strip leading whitespace based on minimum indent of non-empty lines. The first line (if blank after """) and the last line (if whitespace-only before """) are dropped.
Numeric literals support _ as a visual separator (e.g., 1_000_000).
// line comment (to end of line)
/* block comment (nestable) */
Block comments nest: /* outer /* inner */ still outer */ is valid.
module import type protocol impl for in fn
let var if then else match ok err
some none todo true false
not and or strict pub effect test
guard break continue while local mod
fan
Operators: + - * / % ^ == != < <= > >= |> .. ..=
Unary: - not
Logical: and or
Assignment: =
Arrows: -> =>
Delimiters: ( ) { } [ ] , . : ; | _ @ ...
^is exponentiation (right-associative).**is accepted as an alias+is overloaded: addition for numbers, concatenation for strings and lists..is exclusive range,..=is inclusive range...is spread (in records)_is wildcard (in match patterns) or placeholder (in pipe arguments)@is used for extern annotations
Newlines separate statements. Semicolons are used only to place multiple statements on a single line.
let x = 1
let y = 2
let a = 1; let b = 2
A newline is ignored and the statement continues when:
The line ends with:
- Binary operators:
+,-,*,/,%,==,!=,<=,>=,<,>,and,or,|> - Delimiters:
,,.,: - Opening brackets:
(,{,[ - Arrows:
->,=> - Assignment:
= - Keywords:
if,then,else,match,not,|
The next line starts with:
.(method chaining)|>(pipe)
let result = items
.filter((x) => x > 0)
.map((x) => x * 2)
text
|> string.trim
|> string.split(",")
Program ::= ImportDecl* TopDecl*
TopDecl ::= TypeDecl | TraitDecl | ImplDecl | FnDecl
| TopLetDecl | StrictDecl | TestDecl
Stmt ::= LetStmt | VarStmt | AssignStmt | GuardStmt | Expr
Expr ::= Literal | Name | InterpolatedStr
| RecordExpr | SpreadExpr | ListExpr | MapExpr
| CallExpr | MemberExpr | IndexExpr
| PipeExpr | BinaryExpr | UnaryExpr
| IfExpr | MatchExpr
| ForInExpr | WhileExpr | FanExpr
| BlockExpr | LambdaExpr
| HoleExpr | TodoExpr
| RangeExpr | TupleExpr | UnsafeExpr
| "(" Expr ")"
Package identity is declared in almide.toml. No module declaration in source files.
ImportDecl ::= "import" ModulePath
| "import" ModulePath "as" Ident
| "import" ModulePath "." "{" NameList "}"
ModulePath ::= Ident ( "." Ident )*
Examples:
import fs // stdlib module
import json // stdlib module (not auto-imported)
import mylib // user package
import mylib.parser // sub-module import
import mylib as m // alias: m.hello()
import self as app // self-alias for the current package
import mylib.{Parser, Lexer} // selective import
Prohibited: wildcard imports. import fs.* is a compile error.
The following modules are available without import:
string list int float math map result option value set
All other stdlib modules require explicit import:
fs env io process json random regex datetime http
log testing error
Bundled stdlib packages (pure Almide): args, path, time, encoding, hash, url, csv.
Implicitly imported types and constructors:
- Primitives:
Int,Float,Bool,String,Unit,Path - Collections:
List[T],Map[K, V],Set[T] - Error handling:
Option[T],Result[T, E] - Constructors:
some(x),none,ok(x),err(x) - Booleans:
true,false - Boundary:
Json,Value
fn pub_fn() -> String = ... // public (default)
mod fn internal() -> String = ... // same project only (pub(crate) in Rust)
local fn helper() -> String = ... // same file only (private)
Visibility applies to fn, type, and let declarations:
local type Internal = { data: String }
mod let THRESHOLD = 100
Type arguments use []. <> is reserved exclusively for comparison operators.
GenericParams ::= "[" TypeParam ( "," TypeParam )* "]"
TypeParam ::= TypeName
Result[List[Map[String, Int]], Error]
fn map[U](xs: List[T], f: Fn(T) -> U) -> List[U]
type User = {
id: Int,
name: String,
}
type Pair[A, B] = {
first: A,
second: B,
}
Three variant case forms: unit, tuple-style, and record-style.
type Token =
| Word(String)
| Number(Int)
| Eof
type Shape =
| Circle(Float)
| Rect{ width: Float, height: Float }
| Point
Inline variant (without leading |):
type Direction = North | South | East | West
ConventionClause ::= ":" TypeName ("," TypeName)*
Convention names are specified after the type name with :, before =:
type Color: Eq, Repr =
| Red | Green | Blue
Available conventions: Eq, Repr, Ord, Hash, Codec. Eq and Hash are automatic for all value types and do not need to be declared.
ProtocolDecl ::= "protocol" TypeName "{" ProtocolMethod* "}"
ProtocolMethod ::= ["effect"] "fn" Ident "(" Params ")" "->" TypeExpr
Protocols define sets of required convention methods. Types declare satisfaction with : ProtocolName.
protocol Serializable {
fn serialize(a: Self) -> String
fn deserialize(raw: String) -> Result[Self, String]
}
type Config: Serializable = { key: String, value: String }
fn Config.serialize(c: Config) -> String = c.key + "=" + c.value
fn Config.deserialize(raw: String) -> Result[Config, String] = ...
Self is a placeholder for the implementing type. Built-in conventions (Eq, Repr, Ord, Hash, Codec) are protocols.
Protocols can be used as generic bounds:
fn show[T: Serializable](item: T) -> String = item.serialize()
No dynamic dispatch — all protocol-bounded generics are monomorphized at compile time.
type Score = Int
type Handler = (String) -> String
- Transparent alias —
ScoreandIntare interchangeable - Function type aliases use
(Params) -> Returnsyntax
List[String]
Result[User, ParseError]
Map[String, List[Int]]
(Int, String) // tuple type
(Int, String, Bool) // 3-tuple
Access via .0, .1, etc.
Fn(Int) -> String
Fn(Int, Int) -> Bool
TraitDecl ::= "trait" TypeName GenericParams? "{" TraitMethod* "}"
TraitMethod ::= "effect"? "fn" Name "(" ParamList ")" "->" TypeExpr
trait Iterable[T] {
fn map[U](self, f: Fn(T) -> U) -> Self[U]
fn filter(self, f: Fn(T) -> Bool) -> Self[T]
fn fold[U](self, init: U, f: Fn(U, T) -> U) -> U
}
trait Storage[T] {
effect fn save(self, item: T) -> Result[Unit, IoError]
effect fn load(self, id: String) -> Result[T, IoError]
}
ImplDecl ::= "impl" TypeName GenericParams? "for" TypeName "{" FnDecl* "}"
impl Iterable[T] for List[T] {
fn map[U](self, f: Fn(T) -> U) -> List[U] = _
fn filter(self, f: Fn(T) -> Bool) -> List[T] = _
}
- Eq and Hash are compiler-derived automatically from type structure. No annotation needed.
- Conventions are specified with
:after the type name:type Color: Eq, Repr = ...
Int Float Bool String Path Unit
Bytes is represented as List[Int].
List[T] Map[K, V] Set[T]
Option[T] -- some(x) | none
Result[T, E] -- ok(x) | err(e)
Json Value
Used as receivers for external input. Requires explicit conversion before use in domain logic.
FnDecl ::= Visibility? "effect"? "fn" Name GenericParams?
"(" ParamList? ")" "->" TypeExpr "=" Expr
Visibility ::= "local" | "mod" // default is public
ParamList ::= Param ( "," Param )*
Param ::= Identifier ":" TypeExpr ( "=" Expr )? // default value optional
Modifier order: [local|mod]? effect? fn
- Argument types are required
- Return type is required
- The body is a single expression (after
=) effect fnmarks functions with side effectsfan { }for concurrent execution (only insideeffect fn)
fn add(x: Int, y: Int) -> Int = x + y
effect fn greet(name: String) -> Result[Unit, String] = {
println("Hello, ${name}!")
ok(())
}
Parameters may have default values. All parameters after the first default must also have defaults.
fn connect(host: String, port: Int = 8080, secure: Bool = false) -> Connection = _
Arguments can be named at the call site:
connect("localhost")
connect("localhost", port: 443, secure: true)
connect(host: "localhost", secure: true)
Named arguments after positional ones are allowed. Positional arguments after named ones are not.
A call of a record type (or record-payload variant case) with all-named
arguments is record construction: Cfg(name: "x") is normalized to
Cfg { name: "x" } and validated identically (unknown / duplicate /
missing-without-default fields are E021). Positional arguments on a record,
and named arguments on a tuple constructor, are rejected (E021).
Functions ending in ? must return Bool:
fn empty(xs: List[Int]) -> Bool = list.len(xs) == 0
fn tracked(index: Index, path: Path) -> Bool = _
Calling an effect fn from a non-effect function is a compile error, not a warning.
fn pure_fn() -> String =
fs.read_text("file.txt") // Compile error: cannot call effect fn from non-effect fn
The effect system is a search space reducer for code generation: a pure function can only call other pure functions, shrinking the set of valid completions.
let PI = 3.14159265358979323846
let MAX_RETRIES = 3
let GREETING = "Hello, world"
Evaluated at compile time when possible. Numeric and simple expressions become const; String and complex expressions use LazyLock<T> in Rust codegen.
For FFI bindings to target-specific functions:
@extern(rust, "std::fs", "read_to_string")
@extern(ts, "fs", "readFileSync")
effect fn read_text(path: String) -> Result[String, String] = _
let x = 1 // immutable
let x: Int = 1 // with type annotation
var y = 2 // mutable
y = y + 1 // reassign (var only)
let { name, age } = user // record destructure (one level only)
- Immutable bindings only (no
vardestructure) - Nested destructuring is not allowed
- Renaming is not allowed
guard cond else expr
Precondition checking with early exit. When the condition is false, the else expression is returned.
effect fn validate(x: Int) -> Result[Int, String] = {
guard x > 0 else err("must be positive")
guard x < 1000 else err("too large")
ok(x * 2)
}
Only allowed for identifiers bound with var:
var count = 0
count = count + 1
xs[0] = "updated" // index write (var only)
m["key"] = value // map write (var only)
if cond then expr else expr
elseis mandatory.ifwithoutelseis a syntax error.- The condition must be
Bool. No truthiness. - Chaining:
if a then x else if b then y else z
match subject {
Pattern => expr,
Pattern if guard_cond => expr,
_ => expr,
}
match must be exhaustive (enforced by the type checker).
Guards take a Bool expression after if. When the guard is false, the next arm is tried.
Pattern ::= "_" // wildcard
| Identifier // bind
| Literal // int, float, string, bool
| "some" "(" Pattern ")" // Option
| "none"
| "ok" "(" Pattern ")" // Result
| "err" "(" Pattern ")"
| TypeName // unit constructor
| TypeName "(" Pattern ("," Pattern)* ")" // tuple constructor
| TypeName "{" FieldPattern* ".."? "}" // record constructor
| "(" Pattern "," Pattern+ ")" // tuple
FieldPattern ::= Ident (":" Pattern)?
Record patterns support .. for partial matching:
match shape {
Circle(r) => 3.14 * r * r,
Rect{ width, height } => width * height,
Rect{ width, .. } => width,
Point => 0.0,
}
(x) => expr
(x, y) => expr
(x: Int) => x + 1
items.map((x) => x * 2)
The body is any expression, including block expressions for multi-line lambdas:
let f = (x) => {
let y = x * 2
y + 1
}
One form only. Lambda parameters may optionally include type annotations.
The last expression in a block is its value:
{
let x = 1
let y = 2
x + y
}
for item in items {
println(item)
}
for (k, v) in map.entries(config) {
println(k + " = " + v)
}
for i in 0..10 {
println(int.to_string(i))
}
Iterates over a list. The loop body is Unit-typed. Use for...in for collection iteration.
var i = 0
while i < 10 {
println(int.to_string(i))
i = i + 1
}
Loops while the condition is true. The loop body is Unit-typed.
fan { expr1; expr2; expr3 }
Runs expressions concurrently. Returns results as a tuple. Only valid inside effect fn.
Rules:
- If any expression returns
Err, the entirefanfails and siblings are cancelled - No
let/var/for/whileinsidefanblocks -- only expressions - No
varcapture from outer scope (prevents data races)
Library forms:
fan.map(xs, f)-- parallel map over a collectionfan.race(thunks)-- first to complete wins, rest cancelled
text |> string.trim |> string.split(",")
x |> f is equivalent to f(x).
Placeholder _ for multi-argument functions:
xs |> list.filter(_, (x) => x > 0)
text |> string.split(_, ",")
_ specifies where the piped value is inserted. Multiple _ in a single call is a compile error.
Pipe into match:
value |> match {
some(x) => x,
none => "default",
}
f(x, y) and x.f(y) are equivalent. The compiler resolves automatically.
string.trim(text) // canonical form
text.trim() // UFCS form -- equivalent
string.split(text, ",")
text.split(",") // equivalent
Resolution: when x.f(args...) is called, the compiler looks for f(x, args...) in scope.
0..5 // [0, 1, 2, 3, 4] exclusive end
1..=5 // [1, 2, 3, 4, 5] inclusive end
for i in 0..n { ... } // no list allocation (optimized)
let alice = { name: "alice", age: 30 }
let bob = { ...alice, name: "bob" } // age inherited from alice
{ name } // shorthand: { name: name }
[1, 2, 3]
[] // empty list (type inferred from context)
xs[0] // index access
xs[i] = value // index write (var only)
["a": 1, "b": 2] // map literal
[:] // empty map (requires type annotation)
let m: Map[String, Int] = [:]
m["key"] // index access (returns Option[V])
m["key"] = value // index write (var only)
(1, "hello") // tuple literal
let pair = (1, "hello")
pair.0 // index access: 1
pair.1 // "hello"
let name = "world"
let msg = "hello ${name}, 1+1=${1 + 1}"
Works in double-quote strings, single-quote strings, and heredocs (but not raw strings).
fn parse(text: String) -> Ast = _ // hole (type-checked stub)
fn optimize(ast: Ast) -> Ast = todo("implement later") // todo with message
_ (hole) and todo(msg) accept any expected type. The compiler reports the expected type, available variables, and suggestions.
fn technically_pure(x: Int) -> Int =
unsafe { fs.read_text(cache_path) } // bypass effect boundary
unsafe surfaces "normal rules are being broken here."
| Level | Operators | Associativity |
|---|---|---|
| 1 | |> (pipe) |
left |
| 2 | or |
left |
| 3 | and |
left |
| 4 | == != < <= > >= |
left |
| 5 | .. ..= (range) |
none |
| 6 | + - (additive) |
left |
| 7 | * / % ^ (multiplicative, XOR) |
left |
| 8 | ** (power) |
right |
| 9 | - not (unary) |
prefix |
| 10 | . () [] (postfix) |
left |
- Assignment (
=) is a statement, not an operator - Operator overloading is prohibited -- built-in types only
&&and||are rejected with hints to useand/or
| Operator | Meaning |
|---|---|
+ |
Addition (Int, Float) or concatenation (String, List) |
- * / % |
Arithmetic |
** |
Exponentiation |
^ |
Bitwise XOR (Int) |
== != |
Deep equality (all types except Fn) |
< <= > >= |
Comparison |
and or not |
Boolean logic |
|> |
Pipe |
.. ..= |
Range (exclusive / inclusive) |
In Rust codegen, ==/!= emit the almide_eq! macro for deep structural equality. In WASM codegen, they emit byte-level comparison.
| Layer | Mechanism | Use Case |
|---|---|---|
| Normal failure | Result[T, E] |
parse, validate, I/O, lookup |
| Programmer error | panic |
unreachable, invariant violations |
| Testing | assert_eq, assert |
test assertions |
Exceptions do not exist. There is no throw/catch.
Three postfix operators for explicit error handling on Result[T, E] and Option[T]:
expr! // unwrap — propagate err on failure (effect fn only)
expr ?? val // unwrap or — use fallback on failure
expr? // to option — convert Result to Option (err → none)
effect fn load(path: String) -> Result[Config, String] = {
let text = fs.read_text(path)! // err → propagates
let config = json.parse(text)! // err → propagates
ok(config)
}
let port = int.parse(input) ?? 8080 // err → use 8080
let name = map.get(config, "key") ?? "default" // none → use "default"
For branching on error kinds, use a variant type as the error parameter:
type LoadError = | NotFound(String) | ParseFailed(String)
effect fn load(path: String) -> Result[Config, LoadError] = {
let text = fs.read_text(path)
|> result.map_err((e) => NotFound(e))!
let config = json.parse(text)
|> result.map_err((e) => ParseFailed(e))!
ok(config)
}
match load("app.toml") {
ok(config) => use(config),
err(NotFound(path)) => create_default(),
err(ParseFailed(msg)) => println("Bad config: ${msg}"),
}
Core feature of the language. Allows incremental development.
fn parse(text: String) -> Ast = _
fn optimize(ast: Ast) -> Ast = todo("implement constant folding")
When a hole is found, the compiler returns:
- Expected type
T - Available variables in scope and their types
- Function candidates that can return the expected type
- Suggested expressions
fan { } runs expressions concurrently. Only valid inside effect fn.
effect fn main() -> Result[Unit, String] = {
let (a, b) = fan {
fetch_user(1)
fetch_posts(1)
}
println("${a}, ${b}")
ok(())
}
Results are returned as a tuple. If any expression returns Err, the entire fan fails and siblings are cancelled.
let results = fan.map(urls, (url) => fetch(url)) // parallel map
let first = fan.race([task_a, task_b]) // first to complete wins
fan { }only insideeffect fn— pure functions cannot fork- No
varcapture — onlyletbindings from outer scope (prevents data races) - No unstructured
spawn— all concurrency is scoped - Fail-fast semantics — first error cancels all siblings
test "description" {
assert_eq(add(1, 2), 3)
assert(x > 0)
assert_ne(a, b)
}
Tests are top-level declarations in the same file as functions. No separate test files required (convention: *_test.almd for dedicated test files).
assert(cond: Bool) // fails if false
assert_eq(actual: T, expected: T) // fails if actual != expected
assert_ne(actual: T, expected: T) // fails if actual == expected
almide test # all .almd with test blocks (recursive)
almide test spec/lang/ # directory
almide test spec/lang/expr_test.almd # single file
almide test --run "pattern" # filter by namestrict types // require all type annotations
strict effects // fully check effect propagation
Declared at the top of a file. Enables stricter compiler checking.
? suffix on function names: is_empty, exists, contains. Return type must be Bool.
The is_ prefix convention is used for predicates in the stdlib: string.is_empty(s), string.is_digit(s).
| Convention | Rule | Example |
|---|---|---|
| Module prefix | module.function() canonical form |
string.len(s), list.get(xs, i) |
| Predicate suffix | ? for boolean-returning functions |
string.is_empty(s), fs.exists(path) |
| No synonyms | One name per operation | len not length/size/count |
| Symmetric pairs | Matching names for inverses | split/join, to_string/to_int |
| Return type consistency | Fallible lookups return Option, I/O returns Result |
list.get() -> Option[T] |
834 functions across 39 modules, defined in pure Almide (stdlib/*.almd). Runtime implementation: 100%.
string (41 functions):
trim, trim_start, trim_end, split, join, len, lines, pad_start, pad_end, starts_with, ends_with, slice, to_bytes, from_bytes, contains, to_upper, to_lower, to_int, replace, char_at, chars, index_of, repeat, count, reverse, is_empty, is_digit, is_alpha, is_alphanumeric, is_whitespace, strip_prefix, strip_suffix, capitalize, is_upper, is_lower, codepoint, from_codepoint, replace_first, last_index_of, to_float
list (54 functions):
len, get, get_or, first, last, sort, sort_by, reverse, contains, index_of, any, all, each, map, flat_map, filter, find, fold, enumerate, zip, flatten, take, drop, chunk, unique, join, sum, product, min, max, is_empty, push, pop, insert, remove, concat, slice, range, count, find_index, partition, scan, window, zip_with, unzip, group_by, frequencies, intersperse, reduce, take_while, drop_while, dedup, rotate, transpose
map (16 functions):
new, get, get_or, set, contains, remove, merge, keys, values, len, entries, from_list, is_empty, map_values, filter, fold
int (21 functions):
to_string, to_hex, parse, parse_hex, abs, min, max, band, bor, bxor, bshl, bshr, bnot, wrap_add, wrap_mul, rotate_right, rotate_left, to_u32, to_u8, clamp, signum
float (16 functions):
to_string, to_int, from_int, round, floor, ceil, abs, sqrt, parse, min, max, clamp, is_nan, is_infinite, signum, truncate
math (21 functions):
min, max, abs, pow, pi, e, sin, cos, tan, log, exp, sqrt, asin, acos, atan, atan2, log2, log10, floor, ceil, round
result (9 functions):
map, map_err, and_then, unwrap_or, is_ok, is_err, ok, err, flatten
option (9 functions):
map, and_then, unwrap_or, ok_or, is_some, is_none, flatten, filter, zip
value (19 functions): Type-agnostic value operations for dynamic data handling.
set (auto-imported):
Set operations: new, insert, contains, remove, union, intersection, difference, len, is_empty, to_list, from_list.
fs (24 functions, all effect):
read_text, read_bytes, read_lines, write, write_bytes, append, mkdir_p, exists, is_dir, is_file, remove, list_dir, copy, rename, remove_dir, metadata, glob, walk_dir, read_dir, create_dir, symlink, read_link, canonical, temp_dir
env (9 functions, effect):
unix_timestamp, millis, args, get, set, cwd, sleep_ms, home_dir, hostname
io (3 functions, effect):
read_line, print, read_all
process (6 functions, effect):
exec, exec_status, exit, stdin_lines, spawn, pid
json (36 functions):
parse, stringify, stringify_pretty, get, get_string, get_int, get_float, get_bool, get_array, keys, to_string, to_int, as_string, as_int, as_float, as_bool, as_array, object, s, i, f, b, null, array, from_string, from_int, from_float, from_bool, from_map, is_null, is_string, is_int, is_array, is_object, len, merge
random (4 functions, effect):
int, float, choice, shuffle
datetime (21 functions):
Date/time operations: now, year, month, day, hour, minute, second, weekday, to_iso, from_parts, etc.
log (8 functions, effect):
debug, info, warn, error, trace, set_level, with_context, flush
testing (7 functions):
assert, assert_eq, assert_ne, assert_ok, assert_err, assert_some, assert_none
error (3 functions):
message, chain, context
regex (8 functions):
match, full_match, find, find_all, replace, replace_first, split, captures
http (26 functions, effect): HTTP client operations.
| Module | Functions |
|---|---|
| args | 6 |
| path | 7 |
| time | 20 |
| encoding | 10 |
| hash | 3 |
| url | 21 |
| csv | 9 |
Available everywhere without import:
println(s: String) // print line to stdout
eprintln(s: String) // print line to stderr
assert_eq(a: T, b: T) // assert equal
assert_ne(a: T, b: T) // assert not equal
assert(cond: Bool) // assert true
There is no print function (use io.print for no-newline output). println requires String -- no implicit conversion.
effect fn main(args: List[String]) -> Result[Unit, AppError] = {
let cmd = list.get(args, 1)
match cmd {
some("run") => do_something(),
some(other) => err(UnknownCommand(other)),
none => err(NoCommand),
}
}
args[0] is the program name, args[1] is the first argument. The runtime calls main(args).
Almide compiles to multiple targets:
| Target | Status | Approach |
|---|---|---|
| Rust | Production | Full ownership analysis, borrow/clone passes |
| WASM | Production | Direct emit (linear memory, WASI) |
Three-layer architecture: IR -> Nanopass -> Templates.
IrProgram (typed IR)
|
Layer 1: Core IR normalization (target-agnostic)
|
Layer 2: Semantic Rewrite (target-specific Nanopass pipeline)
|
Layer 3: Template Renderer (TOML-driven syntax output)
|
Target source code
Rust pipeline passes:
- TypeConcretization -- resolve generic types
- BorrowInsertion -- insert
&references - CloneInsertion -- insert
.clone()calls - StdlibLowering -- module calls to named calls with arg decoration
- ResultPropagation -- insert
?for effect fn calls - BuiltinLowering -- assert_eq, println, etc. to Rust macros
- FanLowering -- fan blocks to tokio::join!/spawn
Templates are defined in TOML files (codegen/templates/*.toml), separating syntax from semantics.
| Feature | Rust | WASM |
|---|---|---|
Result[T, E] |
Result<T, String> |
Tag-based (0=Ok, 1=Err) |
Option[T] |
Option<T> |
Tag-based (0=None, 1=Some) |
effect fn |
returns Result<T, String>, auto ? |
Tag check + early return |
== / != |
almide_eq! macro (deep) |
Byte comparison |
+ on String |
format! / owned concat |
string_concat runtime |
+ on List |
[...a, ...b] |
list_concat runtime |
fan { } |
tokio::join! |
Sequential (single-threaded) |
| # | Prohibited | Reason |
|---|---|---|
| 1 | Implicit type conversion | LLMs mix up types |
| 2 | Truthiness | Conditions must be Bool only |
| 3 | Operator overloading | Operator meaning must not change by type |
| 4 | Exceptions (throw/catch) |
Control flow invisible |
| 5 | Multiple lambda notations | Generation distribution disperses |
| 6 | Internal DSLs | Strong context dependence |
| 7 | Wildcard imports | Source of names unknown |
| 8 | null |
Unified under Option[T] |
| 9 | API aliases | More vocabulary = more hallucinations |
| 10 | <> generics |
Ambiguous with comparison operators |
| 11 | Monkey patching / open classes | Runtime semantic changes invisible |
| 12 | Macros and reflection | Language surface must be fixed |
| 13 | return keyword |
Last expression is always the value |
| 14 | class keyword |
Use type for records and variants |
Cascading derived errors are suppressed. A single root cause is presented.
{
"kind": "type_mismatch",
"location": { "file": "app.almd", "line": 12, "col": 5 },
"expected": "Result[Config, IoError]",
"actual": "String",
"suggestions": ["ok(text)", "parse_config(text)"]
}The compiler recognizes common syntax from other languages and provides actionable hints:
'!' is not valid in Almide at line 5:12
Hint: Use 'not x' for boolean negation, not '!x'.
'return' is not valid in Almide at line 12:5
Hint: Use the last expression as the return value, or 'guard ... else' for early exit.
'||' is not valid in Almide at line 8:10
Hint: Use 'or' for logical OR.
'&&' is not valid in Almide at line 8:10
Hint: Use 'and' for logical AND.
- Missing imports
- Type conversion candidates
- Missing match arms
- Missing
effectmodifier - Suggest
effect fnwhen pure function calls effectful code
almide fmt app.almd- One AST yields exactly one formatted output
- Alphabetical import order
- Trailing commas included
- Fixed line-breaking rules
G(x) = T
-----------
G |- x : T
G |- e : T
--------------------
G, x:T |- let x = e
G |- c : Bool G |- t : T G |- e : T
-------------------------------------------
G |- if c then t else e : T
G, x1:T1, ..., xn:Tn |- body : R
------------------------------------------
G |- fn f(x1:T1,...,xn:Tn) -> R = body
G |- e : T G |- e : T
-------------------- --------------------
G |- some(e) : Option[T] G |- ok(e) : Result[T, E]
G |- e : E
--------------------
G |- err(e) : Result[T, E]
G |- e : T
G,p1 |- e1 : R ... G,pn |- en : R
exhaustive(p1...pn, T)
---------------------------------------
G |- match e { p1=>e1, ..., pn=>en } : R
G |- x : A G |- f : A -> B
-------------------------------
G |- x |> f : B
G |- base : { f1:T1, ..., fn:Tn }
G |- ei : Ti (for overridden fields)
---------------------------------------
G |- { ...base, fi: ei, ... } : { f1:T1, ..., fn:Tn }
G |- e1 : Result[T1, E] G |- e2 : Result[T2, E] enclosing function is effect
------------------------------------------------------------------------------------
G |- fan { e1; e2 } : Result[(T1, T2), E]
G |- cond : Bool G |- else_ : R
return_type = R
---------------------------------------
G |- guard cond else else_
expected_type = T expected_type = T
------------------- -------------------
G |- _ : T G |- todo(msg) : T
import fs
import json
type Config = {
root: String,
bare: Bool,
description: String,
}
type ConfigError =
| Io(String)
| Parse(String)
fn default_config(root: String) -> Config =
{ root: root, bare: false, description: "" }
fn with_description(config: Config, desc: String) -> Config =
{ ...config, description: desc }
fn summary(config: Config) -> String =
"root=${config.root}, bare=${config.bare}"
effect fn load(path: String) -> Result[Config, ConfigError] = {
let text = fs.read_text(path)
let raw = json.parse(text)
json.get_string(raw, "root")
|> option.ok_or("missing root")
|> result.map((r) => { root: r, bare: false, description: "" })
}
effect fn main(args: List[String]) -> Result[Unit, ConfigError] = {
let path = match list.get(args, 1) {
some(p) => p,
none => "config.json",
}
let config = load(path)
println(summary(config))
ok(())
}
test "default config" {
let cfg = default_config("/repo")
assert_eq(cfg.bare, false)
assert_eq(cfg.description, "")
}
test "with_description updates correctly" {
let cfg = default_config("/repo")
let updated = with_description(cfg, "my repo")
assert_eq(updated.description, "my repo")
assert_eq(updated.root, cfg.root)
}
| Metric | Definition |
|---|---|
| Pass@1 | Rate of passing compilation + tests on the first generation |
| Repair Turns | Number of fix iterations from first failure to final success |
| Token Cost | Total input/output tokens until success |
| API Hallucination Rate | Rate of nonexistent APIs or incorrect signatures |
| Edit Breakage Rate | Rate of breaking unrelated behavior when modifying code |
| Diagnostic Utilization Gain | Performance difference with/without structured diagnostics |