Conventions and Idioms
This document collects conventions and best practices. It is a living document that will grow as the language matures.
Naming
Case Style: snake_case
Use snake_case for all identifiers in rill:
# variables
"hello" => $user_name
[1, 2, 3] => $item_list
true => $is_valid
# closures
|x|($x * 2) => $double_value
|s|($s -> .trim) => $cleanup_text
# dict keys
[first_name: "Alice", last_name: "Smith", is_active: true] => $userVariables
Use descriptive snake_case names with $ prefix:
"hello" => $greeting # good: descriptive
"hello" => $g # avoid: too terseFor loop variables, short names are acceptable when scope is small:
[1, 2, 3] -> each |x| ($x * 2) # fine: small scopeClosures
Name closures for their action:
|x|($x * 2) => $double # verb describing transformation
|s|($s -> .trim) => $cleanup # verb describing action
||{ $.count * $.price } => $total # noun for computed valueCapture and Flow
Prefer inline capture when continuing the chain
Capture mid-chain with => to store and continue:
# good: capture and continue
prompt("Read file") => $raw -> log -> .contains("ERROR") ? {
error("Failed: {$raw}")
}
# less clear: separate statements
prompt("Read file") => $raw
$raw -> log
$raw -> .contains("ERROR") ? { error("Failed: {$raw}") }Use explicit capture before branching
Capture values before conditionals when you need them in multiple branches:
# good: $result available in both branches
checkStatus() => $result
$result -> .contains("OK") ? {
"Success: {$result}"
} ! {
"Failed: {$result}"
}Collection Operators
Choose the right operator
| Use case | Operator | Why |
|---|---|---|
| Transform each element | map | Parallel, all results |
| Transform with side effects | each | Sequential order |
| Keep matching elements | filter | Parallel filter |
| Reduce to single value | fold | Final result only |
| Running totals | each(init) | All intermediate results |
| Find first match | each + break | Early termination |
Prefer method shorthand in collection operators
# good: concise
["hello", "world"] -> map .upper
# equivalent but verbose
["hello", "world"] -> map { $.upper() }
["hello", "world"] -> map |x| $x.upper()Method chains work too:
[" HELLO ", " WORLD "] -> map .trim.lowerUse grouped form for negation
# correct: grouped negation
["", "a", "b"] -> filter (!.empty)
# wrong: .empty returns truthy elements
["", "a", "b"] -> filter .empty # returns [""]Use fold for reduction, each(init) for running totals
# sum: use fold (returns final value)
[1, 2, 3] -> fold(0) { $@ + $ } # 6
# running sum: use each (returns all intermediates)
[1, 2, 3] -> each(0) { $@ + $ } # [1, 3, 6]Break returns partial results in each
[1, 2, 3, 4, 5] -> each {
($ == 3) ? break
$ * 2
}
# Result: [2, 4] (elements processed BEFORE break)Loops
Use $ as accumulator in while/do-while
# good: $ accumulates naturally
0 -> ($ < 5) @ { $ + 1 }
# avoid: named variables don't persist across iterations
0 -> ($ < 5) @ {
$ => $x # $x exists only in this iteration
$x + 1
}Prefer do-while for retry patterns
Do-while runs body at least once, eliminating duplicate first-attempt code:
# good: body runs at least once
@ {
attemptOperation()
} ? (.contains("RETRY"))
# less clean: separate first attempt
attemptOperation() => $result
$result -> .contains("RETRY") @ {
attemptOperation()
}Use each for collection iteration, not while
# good: each is designed for collections
$items -> each { process($) }
# avoid: manual iteration with while
$items -> .first() -> (!$.done) @ {
process($.value)
$.next()
}Conditionals
Condition must be boolean
The condition in cond ? then ! else must evaluate to boolean:
# correct: .contains() returns boolean
"hello" -> .contains("ell") ? "found" ! "not found"
# correct: comparison returns boolean
5 -> ($ > 3) ? "big" ! "small"Use ?? for defaults, not conditionals
# good: concise default
$dict.field ?? "default"
# avoid: verbose conditional
$dict.?field ? $dict.field ! "default"Chain conditionals for multi-way branching
($status == "ok") ? {
"Success"
} ! ($status == "pending") ? {
"Waiting"
} ! {
"Unknown: {$status}"
}Multi-line conditionals for readability
Use 2-space indent for ? and ! continuations:
# good: multi-line conditional
some_condition
? "yes"
! "no"
# good: piped conditional split
value -> is_valid
? "ok"
! "error"
# good: chained else-if
$val -> .eq("A") ? "a"
! .eq("B") ? "b"
! "c"
# avoid: inconsistent indent
condition
? "yes"
! "no"Closures
Use braces for complex bodies
# simple: parentheses ok
|x|($x * 2) => $double
# complex: braces required
|n| {
($n < 1) ? 1 ! ($n * $factorial($n - 1))
} => $factorialCapture loop variable explicitly for deferred closures
# good: explicit capture per iteration
[1, 2, 3] -> each {
$ => $item
|| { $item }
} => $closures
# result: closures return [1, 2, 3] when calledDict closures for computed properties
Zero-arg closures auto-invoke when accessed:
[
items: [1, 2, 3],
count: ||{ $.items -> .len }
] => $data
$data.count # 3 (auto-invokes)Parameterized closures work as methods:
[
name: "test",
greet: |x|{ "{$.name}: {$x}" }
] => $obj
$obj.greet("hello") # "test: hello"Type Safety
Annotate closure parameters for clarity
|name: string, count: number| {
"{$name}: {$count}"
} => $formatCapture with type annotation for documentation
"processing" => $status:stringUse type assertions sparingly
Type assertions (:type) are for validation, not conversion:
# good: validate external input
fetch_data($url):dict => $data
# unnecessary: type is already known
5:number => $nString Handling
Use triple-quotes for multiline content
"""
Analyze this content:
{$content}
Provide a summary.
"""Use .empty for emptiness checks
# idiomatic: use .empty property
"" -> .empty ? "empty" ! "not empty"Direct string comparison works but .empty is preferred:
# works, but verbose
$str == "" ? "empty"
# idiomatic: clearer intent
$str -> .empty ? "empty"Chain string methods naturally
" HELLO world " -> .trim.lower.split(" ")Error Handling
Validate early with conditionals
$input -> .empty ? { error("Input required") }
# continue with validated input
process($input)Use explicit signals for workflow control
prompt("...") => $result
$result -> .contains(":::ERROR:::") ? {
error("Operation failed: {$result}")
}
$result -> .contains(":::DONE:::") ? {
"Complete" -> return
}Anti-Patterns
Avoid reassigning variables
Variables lock to their first type. Reassigning suggests misuse:
# avoid: confusing reassignment
"initial" => $x
"updated" => $x # works but unclear
# prefer: new variable or functional style
"initial" -> transform() => $resultAvoid bare $ in stored closures
# confusing: what is $?
|| { $ + 1 } => $fn # $ is undefined when called
# clear: explicit parameter
|x| { $x + 1 } => $fnAvoid break in parallel operators
Break is not supported in map or filter (they run in parallel):
# wrong: break in map
[1, 2, 3] -> map { ($ > 2) ? break }
# correct: use each if you need break
[1, 2, 3] -> each { ($ > 2) ? break }
# or filter first
[1, 2, 3] -> filter { $ <= 2 } -> map { $ }Avoid complex logic in conditions
# hard to read
(($x > 5) && (($y < 10) || ($z == 0))) ? { ... }
# clearer: extract to named check
($x > 5) => $big_enough
(($y < 10) || ($z == 0)) => $valid_range
($big_enough && $valid_range) ? { ... }Formatting
Spacing Rules
Operators: space on both sides
# good
5 + 3
$x -> .upper
"hello" => $greeting
($a == $b) ? "yes" ! "no"
# avoid
5+3
$x->.upper
"hello"=>$greetingParentheses: no inner spaces
# good
($x + 1)
($ > 3) ? "big"
[1, 2, 3] -> each |x| ($x * 2)
# avoid
( $x + 1 )
( $ > 3 ) ? "big"Braces: space after { and before }
# good
{ $x + 1 }
[1, 2, 3] -> each { $ * 2 }
|x| { $x -> .trim }
# avoid
{$x + 1}
[1, 2, 3] -> each {$ * 2}Multiline braces: opening brace on same line, closing on own line
# good
[1, 2, 3] -> each {
$ => $item
$item * 2
}
# avoid
[1, 2, 3] -> each
{
$ * 2
}Brackets: no inner spaces for indexing
# good
$list[0]
$dict.items[1]
# avoid
$list[ 0 ]List/dict literals: space after colons and commas
# good
[1, 2, 3]
[name: "alice", age: 30]
# avoid
[1,2,3]
[name:"alice",age:30]Closure parameters: no space before pipe, space after
# good
|x| ($x * 2)
|a, b| { $a + $b }
|| { $.count }
# avoid
| x | ($x * 2)
|a,b|{ $a + $b }Method calls: no space before dot or parentheses
# good
$str.upper()
$list.join(", ")
"hello" -> .trim.lower
# avoid
$str .upper()
$list.join (", ")Pipes: space on both sides of -> and =>
# good
"hello" -> .upper -> .len
"value" => $x -> log
# avoid
"hello"->.upper->.len
"value"=>$x->logImplicit $ shorthand: prefer sugared forms
# methods: $.foo() -> .foo
# good
"hello" -> .upper -> .len
[1, 2, 3] -> map .str
# avoid
"hello" -> $.upper() -> $.len
[1, 2, 3] -> map $.str
# global functions: foo($) -> foo
# good
"hello" -> log -> .upper
42 -> type
# avoid
"hello" -> log($) -> .upper
42 -> type($)
# closures: $fn($) -> $fn
# good
|x| ($x * 2) => $double
5 -> $double
# avoid
5 -> $double($)No throwaway captures: don’t capture just to continue
# avoid: unnecessary intermediate variables
"hello" => $x
$x -> .upper => $y
$y -> .len
# good: use line continuation instead
"hello"
-> .upper
-> .len
# good: capture only when reused later
"hello" => $input
$input -> .upper => $upper
"{$input} became {$upper}" # both variables referencedChain continuations: indent continued lines by 2 spaces
# good: align continuation with pipe
$data
-> .filter { $.active }
-> map { $.name }
-> .join(", ")
# good: long method chains
" hello world "
-> .trim
-> .upper
-> .split(" ")
-> .join("-")
# good: capture mid-chain
prompt("analyze {$file}")
=> $result
-> log
-> .contains("ERROR") ? { error($result) }
# good: conditional continuation
value -> is_valid
? "ok"
! "error"
# good: split else-if chain
$val -> .eq("A") ? 1
! .eq("B") ? 2
! 3
# avoid: no indent on continuation
$data
-> .filter { $.active }
-> map { $.name }One statement per line for complex code
# good: clear structure
$input -> validate() => $valid
$valid -> process() => $result
$result -> format()
# acceptable for simple chains
$input -> .trim -> .lower -> .split(" ")Indent block contents
{
"first" => $a
"second" => $b
"{$a} {$b}"
}Align related captures
prompt("Get name") => $name
prompt("Get age") => $age
prompt("Get role") => $roleThis document will be extended as conventions emerge from real-world usage.
See Also
- Design Principles — Core philosophy
- Reference — Language specification
- Guide — Getting started tutorial