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

ItemConventionExample
Variablessnake_caseread_count, gc_value
Functionssnake_casefilter_reads, compute_stats
StructsPascalCaseGenomicInterval, AlignConfig
EnumsPascalCaseStrand, VariantType
Enum variantsPascalCaseStrand.Plus, VariantType.Snv
ConstantsSCREAMING_SNAKEMAX_QUALITY, MIN_MAPQ
File namessnake_case.blquality_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}")