Idioms
Use the forms on this page as the defaults for ordinary Yulang code. They keep calls and control flow readable while taking advantage of the language's punctuation-free forms.
Bare application
Use f x y as the everyday call form. Reach for f(x, y) only to group arguments visually or when an argument is a literal that would otherwise chain into the next token; for example, f(-1) can be clearer than f -1.
// Idiomatic
add 1 2
greet name
read_text path
// Less idiomatic in Yulang (still legal)
add(1, 2)
greet(name)
read_text(path)Colon application for big tails
When the final argument is a block or another long expression, use : to push it to the right.
catch action:
log::put msg, k -> handle msg
v -> v
run_console:
my answer = ask()
say answer
io_err::wrap:
read_text pathf x: body reads "apply f to x and then to the colon body". This is the canonical way to invoke handler-like and block-shaped APIs.
Method dot chains
Keep the dot tight for the first selection, and put a space before later dots that should land on the outer bare-application expression. Selection itself is not application: xs.map selects a function, and the following argument applies it. The space before .filter below closes the current argument, so the chain means (xs.map double) .filter ..., not xs.map (double.filter ...).
xs.map double .filter (\x -> x > 0) .lenAt the top level both spellings work — xs.map and xs .map resolve to the same field selection. The difference only matters when the dotted expression sits inside an ML-style argument list. See Application for the precise rule.
with: blocks for companion methods
Put companion methods in the declaration's with: block. Method headers our recv.name args = body make value.name args resolve to the method without an extra self parameter declaration.
struct point { x: int, y: int } with:
our p.norm2 = p.x * p.x + p.y * p.y
our p.scale n = point { x: p.x * n, y: p.y * n }Attached impl inside with:
Use an attached impl to avoid spelling the enclosing struct name twice. The enclosing type is prepended as the role's first argument; write the remaining role arguments after the role name.
struct box 'a { value: 'a } with:
impl Index int:
type value = 'a
our b.index _ = b.valueReceiver style in roles
Write role methods in receiver form. our a.method: T gives implementors value.method of type T; the receiver name is informational, so choose one that makes the role read clearly.
role Eq 'a:
our a.eq: 'a -> bool
role Add 'a:
our a.add: 'a -> 'aerror E: over hand-rolled enums
Use error E: instead of writing the enum, effect, Throw and Display implementations, and wrap helper by hand. An up helper is also generated when the declaration has from entries. Use the long form only when the generated surface does not fit.
pub error path_err:
not_found path
denied pathfail e over e.throw
Use fail to surface an error value into the effect row. The prefix form makes the throw site easy to spot while scanning a function.
fail path_err::not_found pathsub: / return over deep nesting
Use sub: and return to keep the successful path flat instead of nesting conditionals. sub: opens the early-return scope.
sub:
if not config.valid: return default
my parsed = parse config
if parsed.empty: return default
process parsed$x / &x for local mutability
Use explicit reference syntax when a local mutable cell is needed. The compiler turns it into a handled var effect, so mutation remains visible to the type system.
my incremented =
my $count = 0
&count = $count + 1
$countEffectful if
Use an effectful condition when the condition itself is nondeterministic. std::control::junction supplies the effectful boolean operations accepted by if; ordinary bool conditions follow the usual path.
if all [1, 2, 3] < any [2, 3, 4]:
"overlap"
else:
"no overlap"Lean on inference, annotate at boundaries
Let inference recover local types, and annotate public API boundaries, generic constraints, or residual variables that need to be fixed. The pipeline x | f feeds the left-hand value to the right-hand expression, like F# or Elixir's |>, but with one bar.
my parse text = text
my render text = text
pub our_pipeline = "data" | parse | render
pub our_pipeline_typed(value: str): str =
value | parse | renderSee also
- Syntax Style — the precise whitespace and colon rules
- Cookbook — task-oriented recipes
- Pitfalls — common gotchas