ilusm.dev

Effective ilusm

Writing clear, idiomatic ilusm code

This document gives tips for writing clear, idiomatic ilusm code. It augments the language specification and the Tour of ilusm with practical examples and best practices.

1. Embrace Brevity

Tip

ilusm is designed for conciseness. Use short names, skip unnecessary keywords, and let the language do the work.

# Verbose (not ilusm style)
# function calculate_sum(numbers_list):
#     total_sum = 0
#     for each number in numbers_list: ...

# Idiomatic ilusm
sum(xs) =
    s = 0
    x <- xs: s = s + x
    s

2. Use Pipelines for Data Flow

Tip

The pipeline operator | makes data transformations readable. Use it instead of nested calls.

# Nested (harder to read)
result = trl.srt(trl.fil(trl.map(data, fn), pred))

# Pipeline (preferred)
result = data | map fn
              | fil pred
              | srt

3. Use def for Constants

Tip

Use def for values that should not change. This prevents accidental reassignment.

def max_r = 3
def tmo = 30
def url = "https://api.example.com"

# Mutable only when necessary
i = 0
whl i < max_r:
    prn(i)
    i = i + 1

4. Use Pattern Matching

Tip

Pattern matching with mat is clearer than chained if statements.

# Chained ifs (avoid)
# if x == 1: prn("one")
# if x == 2: prn("two")
# ...

# Pattern match (preferred)
mat x: 1 => "one" | 2 => "two" | 3 => "three" | _ => "other"

5. Destructure for Clarity

Tip

Destructuring makes it clear what you are extracting from complex data.

# Object destructuring
point = {x: 10, y: 20}
{x, y} = point
prn($"Location: ({x}, {y})")

# List destructuring
[a, b, c] = [1, 2, 3]

6. Write Descriptive Functions

Tip

Even with short names, functions can be clear. Use consistent abbreviations.

# Clear abbreviations
sum(xs) = ...       # sum a list
dbl(x) = x + x     # double a value
sqr(x) = x * x     # square a value

# User identifiers are unrestricted in length
validate_user(usr) = ...
format_date(dt) = ...

7. Handle Errors Explicitly

Tip

Use try for operations that might fail. Always check the err field.

# try(f) calls a 0-arg function, catches errors
# returns {val, err} - err is nil on success
parse() = jsn.dec(raw)
r = try(parse)

if r.err: prn($"Parse failed: {r.err}")
| prn(r.val)

8. Pipeline-Ready Functions

Tip

Design functions to work well in pipelines: take the main data as the first argument.

# trl functions are pipeline-ready: data first
# trl.fil(lst, f), trl.map(lst, f)

# In a pipeline, stage names work without trl. prefix
result = data | fil \(x) x > 0
              | map \(x) x * 2
              | srt

9. Use Rest Parameters

# ...name collects remaining args into a list
log(msg, ...args) =
    prn($"[log] {msg}")
    arg <- args: prn($"  - {arg}")

log("startup", "port=8080", "mode=dev")

10. Organize with Modules

Tip

Import what you need at the top. Use the standard library - it covers most common tasks.

use trl
use txt
use jsn
use fs

# Your code follows...

Common Patterns

CLI Tool

use os
use fs

path = os.arg(1)
if path == nil:
    prn("Usage: ilusm-vm run tool.ilu <file>")
    os.ext(1)

if fs.has(path):
    data = fs.rd(path)
    prn($"Read {len(data)} bytes from {path}")
| prn($"File not found: {path}")

HTTP Server

use web
use jsn

app = web.app()

app.get("/", \(req) "Welcome")

app.get("/api/users", \(req)
    users = [{id: 1, nam: "Alice"}, {id: 2, nam: "Bob"}]
    web.jsn(users)
)

app.run(":8080")

Data Processing

use fs
use jsn
use trl
use txt

data = jsn.dec(fs.rd("input.json"))
lines = data | fil \(r) r.active == tru
             | map \(r) r.nam
             | srt
line <- lines: prn(line)

Anti-Patterns

Avoid: Deep Nesting

# Hard to follow
if a:
    if b:
        if c:
            do_something()

Instead: Use guard clauses or pattern matching.

Avoid: Long Parameter Lists

# Too many params
f(a, b, c, d, e, g) = ...

Instead: Use an object parameter or rest parameters.