Cookbook
Task-oriented recipes for everyday Yulang. Most sections pair an idiomatic form with a brief note; the config, file-editing, log, and larger-example sections develop longer examples. For deeper background, follow the cross-references into the language reference.
The examples lean on Yulang's parens-light style — bare application f x y, colon application f: ..., and indented blocks — rather than C-style calls.
Define a value and a function
my answer = 42
my greet name = "hello, " + name
greet "Yulang"my makes a private binding. Arguments after the name become curried function parameters.
Make a function public
pub double x = x + xpub exports the binding from the current module. our exposes it through the containing companion (typical for methods).
Pattern-match with case
my describe n =
case n:
0 -> "zero"
x if x < 0 -> "negative"
_ -> "positive"Arms are tried top-to-bottom. A guard uses if after the pattern.
Attach a method to a struct
struct point { x: int, y: int } with:
our p.norm2 = p.x * p.x + p.y * p.y
point { x: 3, y: 4 } .norm2with: opens the struct's companion module. The receiver p stands for the value .norm2 is called on.
Implement a role for a type
impl Add point:
our a.add b = point { x: a.x + b.x, y: a.y + b.y }A role declares an interface; an impl provides the methods for a specific type. The receiver a (and second argument b here) match the role's method signatures.
Loop with an early break
sub:
for x in 0..:
if x == 5: return x
0sub: opens an early-return scope. return value exits the innermost sub:. Inside for, last, next, and redo control the loop itself.
Use a mutable local with $x / &x
{
my $count = 0
&count = $count + 1
&count = $count + 1
$count
}my $x = … introduces a local mutable binding. $x reads, &x = value writes. Internally these compile to a small handled var effect, so they fit the effect system rather than escaping it.
Read a text file
my text = read_text "data.txt"
text.saystr widens to path, so a string literal is enough for ordinary paths. Filesystem errors are raised as io_err in the effect row. Wrap them only when the caller needs a value-level result.
my wrapped = io_err::wrap: read_text "data.txt"
case wrapped:
result::ok text -> text
result::err _ -> ""Read a config file
The config, file-editing, and log examples show everyday scripting tasks with small source files under examples/config-file-text/.
This abridged example reads a simple config file with method-form string APIs, trimming both sides of = before converting the port:
my parse_setting(clean: str): opt (str, str) = case clean.split_once "=":
just (key, value) -> just (key.trim, value.trim)
nil -> nil
my read_config(path: str): (int, list (str, str), int, int) =
my $port = 0
my $entries = []
for line in (std::io::file::read_text path).split "\n":
my clean = line.trim
if clean.starts_with "#":
()
else:
case parse_setting clean:
just (key, value) ->
if key == "port":
case value.to_int:
just parsed -> &port = parsed
nil -> ()
&entries = $entries + [(key, value)]
nil -> ()
($port, $entries, 0, 0)Full example: config_read.yu.
Edit a text file by line
For durable line edits, open the file as a text ref and update line views. This abridged example writes a temp copy first, so reruns do not dirty the tracked sample:
use std::control::nondet::*
use std::control::var::*
use std::text::str::*
std::io::file::write_text path (std::io::file::read_text source)
my &doc = std::io::file::text path
((&doc.lines.each).update \line ->
line.replace_once "todo:" "done:"
).list
$doc.sayFull example: file_edit.yu.
Summarize logs with parser patterns
Parser patterns also work well for small log summaries. This abridged example captures the GET payload, filters /api paths with a guard, and parses the captured milliseconds:
use std::text::parse::*
my api_ms(line: str): opt int = case line:
~"GET {rest = ..}" if rest.starts_with "/api" -> case rest.split_once " took ":
just (_, tail) -> case tail.split_once " ms":
just (ms, _) -> ms.to_int
nil -> nil
nil -> nil
_ -> nilFull example: log_stats.yu.
Set up optional arguments
my area {width = 1, height = 2} = width * height
area { width: 3 }
area { width: 3, height: 4 }
area {}A record pattern with defaults gives every field optional behavior at the call site. Defaults evaluate left-to-right and may reference earlier fields.
Raise and catch a typed error
my path = std::text::path::of_bytes (std::text::str::to_bytes "/tmp/data")
catch read_text path:
io_err::not_found _, _ -> "(missing)"
value -> valuefail e is short for e.throw. catch arms name the operation directly, so errors are always caught by their concrete name.
Close an error effect into a result
my path = std::text::path::of_bytes (std::text::str::to_bytes "/tmp/data")
my wrapped = io_err::wrap: read_text path
case wrapped:
result::ok text -> text
result::err err -> err.showE::wrap runs the thunk, catches the matching error effect, and returns a result value. The err side is Display-able through the auto-generated impl.
Aggregate several errors into one
pub error app_err:
file from io_err
parse from parse_err
my read_and_parse path =
app_err::up:
my text = read_text path
parse_json textfrom makes a wider error type wrap narrower ones. app_err::up lifts the narrower errors into app_err inside the block.
Handle a custom effect
pub act log:
pub put: str -> ()
my run_into_strings(action: [log; 'e] 'a): ['e] ('a, list str) =
my $entries = []
my result = catch action:
log::put msg, k ->
&entries = $entries + [msg]
k ()
v -> v
(result, $entries)catch removes the handled operation from the row. Each arm receives the payload and a continuation k. Calling k value resumes the captured computation.
Search nondeterministically
use std::control::nondet::*
(each [1, 2, 3] + each [10, 20]).listeach xs picks one element of xs nondeterministically. .list runs the computation and collects every result. .once, .logic, and friends produce other shapes.
Effectful boolean conditions
if all [1, 2, 3] < any [2, 3, 4]:
"found at least one"
else:
"no overlap"Yulang's if accepts an effectful boolean condition through std::control::junction. all xs and any xs express "every / some element of xs".
Define a cast between two types
struct user_id { raw: int }
cast(x: user_id): int = x.raw
cast(x: int): user_id = user_id { raw: x }
my id: user_id = 7
my raw: int = idcast(x: A): B = body registers a conversion rule from A to B. The compiler applies the rule at expected-type boundaries such as annotations and function arguments.
Larger examples
Combining the recipes above, you can already write small but real programs. Every one of these runs in the Playground as-is.
A tiny expression evaluator
enum plus a recursive function is enough for a calculator:
enum expr =
num int
| add (expr, expr)
| mul (expr, expr)
my eval e =
case e:
expr::num n -> n
expr::add (a, b) -> eval a + eval b
expr::mul (a, b) -> eval a * eval b
eval (expr::add (expr::num 2, expr::mul (expr::num 3, expr::num 4)))
// → 14Carrying a tuple as a variant payload (add (expr, expr)) is the straightforward way to express multi-argument constructors.
Filtering a list with recursion
List spread patterns plus recursion give you a natural filter:
my is_even n =
std::int::eq (std::int::sub n (std::int::mul (std::int::div n 2) 2)) 0
my keep_evens xs =
case xs:
[] -> []
[x, ..rest] ->
my r = keep_evens rest
if is_even x:
[x] + r
else:
r
keep_evens [1, 2, 3, 4, 5, 6, 7, 8]
// → [2, 4, 6, 8]Aggregating in a loop
A local my $x = ... is the natural way to thread a value through a loop:
{
my $best = 0
for y in [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]:
if y > $best: &best = y
$best
}
// → 9$best does not escape the block — the type system makes sure mutable state stays in the scope where it was introduced.
Nondeterministic search
each plus guard lets you describe what you want and have the runtime find it:
use std::control::nondet::*
{
my a = each 1..20
my b = each a..20
my c = each b..20
guard: a * a + b * b == c * c
(a, b, c)
}.list
// → [(3, 4, 5), (5, 12, 13), (6, 8, 10), (8, 15, 17), ...]Swap .list for .once to stop after the first success. Passing each choice into the range of the next prunes the search early.
See also
- Tour — a walk through the same features in narrative form.
- Reference — the full language reference.
- Syntax Style — when and how to drop parentheses.