Style Guide
Conventions for readable, consistent BioLang code. Following these guidelines makes your scripts easier to read and maintain.
Comments
Use # for line comments. There are no block comments.
Doc comments use ##.
# This is a comment
let x = 42 # inline comment
# Multi-line comments: use consecutive # lines
# Good comments explain WHY, not WHAT
## Doc comment: appears before a function declaration
## Extracted by the documentation generator
fn gc_ratio(seq) {
gc_content(seq)
}
println(f"GC of ATCGATCG: {gc_ratio(dna"ATCGATCG")}")
Naming Conventions
| Item | Convention | Example |
|---|---|---|
| Variables | snake_case | read_count, gc_value |
| Functions | snake_case | filter_reads, compute_stats |
| Structs | PascalCase | GenomicInterval, AlignConfig |
| Enums | PascalCase | Strand, VariantType |
| Enum variants | PascalCase | Strand.Plus, VariantType.Snv |
| Constants | SCREAMING_SNAKE | MAX_QUALITY, MIN_MAPQ |
| File names | snake_case.bl | quality_control.bl |
Prefer Pipes Over Nesting
# BAD: deeply nested function calls
let result = reverse(sort(filter([5, 3, 8, 1, 9, 2], |n| n > 3)))
# GOOD: pipe chain, one transform per line
let result = [5, 3, 8, 1, 9, 2]
|> filter(|n| n > 3)
|> sort()
|> reverse()
println(result)
One Transform Per Line
Break pipe chains so each |> stage is on its own line,
indented by 2 spaces:
# GOOD: easy to read, easy to comment out individual stages
let scores = [85, 92, 78, 95, 88, 72, 91]
let top = scores
|> filter(|s| s >= 80)
|> sort()
|> reverse()
|> head(3)
println(f"Top 3 scores: {top}")
Use Meaningful Lambda Names
# BAD: cryptic parameter names
let x = [1, 2, 3, 4, 5] |> filter(|a| a > 2) |> map(|c| c * 10)
# GOOD: descriptive single-letter is fine for simple cases
let big = [1, 2, 3, 4, 5] |> filter(|n| n > 2) |> map(|n| n * 10)
println(big)
Prefer Functional Over Imperative
# BAD: manual loop accumulation
let total = 0
let values = [10, 20, 30, 40]
for v in values {
total = total + v
}
# GOOD: built-in reduction
let total = [10, 20, 30, 40] |> sum()
println(f"Total: {total}")
Handle Errors Explicitly
# BAD: no error handling — crash on failure
# let data = read_csv("missing.csv")
# GOOD: catch and provide a clear message
let data = try {
1 / 0
} catch e {
println(f"Error caught: {e}")
0
}
println(f"Result: {data}")
Named Constants
# BAD: magic numbers without explanation
let filtered = [35, 25, 15, 45] |> filter(|q| q >= 30)
# GOOD: named constant with comment
let MIN_QUALITY = 30 # Phred 30 = 99.9% base call accuracy
let filtered = [35, 25, 15, 45] |> filter(|q| q >= MIN_QUALITY)
println(f"Passed: {len(filtered)} of 4")
Break Long Stages
# Named intermediate stages improve readability
let data = [
{name: "A", score: 85, group: "x"},
{name: "B", score: 92, group: "y"},
{name: "C", score: 78, group: "x"},
{name: "D", score: 95, group: "y"},
]
# Break into named stages instead of one giant chain
let high_scores = data |> filter(|r| r.score >= 80)
let sorted = high_scores |> sort_by("score") |> reverse()
let names = sorted |> map(|r| r.name)
println(f"Top scorers: {names}")