Control Flow
BioLang provides familiar control flow constructs — if/else,
for loops, and while loops. A key distinction is that
if/else are expressions that return values,
not just statements.
If / Else Expressions
Every if/else block evaluates to a value. The last expression
in each branch is the value of that branch:
# if/else as an expression — assigns the result
let label = if score >= 0.9 {
"high"
} else if score >= 0.5 {
"medium"
} else {
"low"
}
# Single-line conditional
let status = if is_coding { "coding" } else { "non-coding" }
# Using if/else in a pipe chain
let category = variant.impact
|> (|impact| if impact == "HIGH" { 1 } else { 0 })
If Without Else
An if without an else branch returns None when
the condition is false. When used as a statement (ignoring the return value), the else
is not required:
# Statement usage — no else needed
if verbose {
print(f"Processing {len(reads)} reads")
}
# As expression without else — returns Option
let maybe_value = if condition { compute_value() }
# maybe_value is None if condition is false
# Guard clause pattern
fn process(data) {
if len(data) == 0 {
return Err("empty input")
}
# Main logic here
Ok(transform(data))
}
Conditional Chains
fn classify_variant(variant) -> String {
if is_snp(variant) && variant.consequence == "synonymous" {
"synonymous_SNV"
} else if is_snp(variant) && variant.consequence == "missense" {
"missense_SNV"
} else if is_snp(variant) && variant.consequence == "nonsense" {
"nonsense_SNV"
} else if is_indel(variant) && variant.consequence == "frameshift" {
"frameshift_indel"
} else if is_indel(variant) {
"inframe_indel"
} else {
"other"
}
}
For Loops
The for loop iterates over any iterable value — lists, ranges,
strings, maps, tables, and streams:
# Iterate over a list
for gene in ["BRCA1", "TP53", "EGFR"] {
print(f"Looking up {gene}")
let info = ncbi_gene(gene)
print(f" Chromosome: {info.chromosome}")
}
# Iterate over a range
for i in 0..10 {
print(f"Index: {i}")
}
# Inclusive range
for i in 1..=100 {
# processes 1 through 100
}
# Iterate with index
for (i, read) in enumerate(reads) {
if i % 1000 == 0 {
print(f"Processed {i} reads")
}
}
Iterating Over Maps
let gene_scores = {"BRCA1": 0.95, "TP53": 0.87, "EGFR": 0.72}
# Key-value destructuring
for (gene, score) in gene_scores {
if score > 0.8 {
print(f"{gene} is significant (score={score})")
}
}
# Keys only
for gene in keys(gene_scores) {
print(gene)
}
# Values only
for score in values(gene_scores) {
print(score)
}
Iterating Over Tables
let variants = read_vcf("calls.vcf")
for row in variants {
print(f"{row.chrom}:{row.pos} {row.ref_allele}>{row.alt_allele}")
}
# Process in chunks
for chunk in variants |> chunk(1000) {
let batch_result = analyze_batch(chunk)
write_results(batch_result)
}
While Loops
# Basic while loop
let attempts = 0
let result = None
while attempts < 3 && result == None {
result = try { fetch_data(url) } catch _ { None }
attempts = attempts + 1
}
# Read until EOF
let lines = []
let line = read_line(file)
while line != None {
lines = push(lines, unwrap(line))
line = read_line(file)
}
Loop Control: Break and Continue
# break exits the loop immediately
for read in fastq("sample.fq") {
if mean_phred(read.quality) < 10 {
print("Found corrupted read, stopping")
break
}
process(read)
}
# continue skips to the next iteration
let good_reads = []
for read in reads {
if read.length < 50 {
continue # Skip short reads
}
if mean_phred(read.quality) < 20 {
continue # Skip low-quality reads
}
good_reads = push(good_reads, read)
}
Break with Values
Loops can return values via break, similar to how if returns
values. This is useful for search patterns:
# Find the first high-quality variant
let found = for variant in variants {
if variant.qual >= 99.0 && variant.depth >= 30 {
break variant
}
}
# found is Option — None if no break was reached
# Search with index
let position = for (i, base) in enumerate(sequence) {
if base == 'G' {
break i
}
}
Nested Loops
# Compare all pairs of samples
let samples = ["sample_a", "sample_b", "sample_c"]
for i in 0..len(samples) {
for j in (i + 1)..len(samples) {
let similarity = compare(samples[i], samples[j])
print(f"{samples[i]} vs {samples[j]}: {similarity}")
}
}
# Labeled break for nested loops
for chrom in chromosomes {
for gene in genes_on(chrom) {
for variant in variants_in(gene) {
if is_pathogenic(variant) {
print(f"Found pathogenic: {variant}")
break # Exits innermost loop
}
}
}
}
Comprehensions vs. Loops
BioLang encourages functional style with map/filter over
imperative loops, but loops are available when they are clearer:
# Functional style (preferred for transforms)
let gc_values = sequences |> map(|s| gc_content(s))
let long_seqs = sequences |> filter(|s| len(s) > 1000)
# Loop style (clearer for side effects)
for sample in samples {
let result = run_pipeline(sample)
write_report(result, f"{sample.name}_report.html")
print(f"Completed {sample.name}")
}
# Loop with accumulation (reduce is often better)
let total = 0
for val in values {
total = total + val
}
# vs.
let total = values |> reduce(|a, b| a + b)
Conditional Expressions in Context
# Ternary-like usage in function calls
let color = if p_value < 0.05 { "red" } else { "gray" }
# Inside string interpolation
print(f"Status: {if passed { "PASS" } else { "FAIL" }}")
# As function arguments
plot(data,
color = if grouped { "category" } else { "none" },
title = if normalized { "Normalized Values" } else { "Raw Values" }
)
# In list construction
let flags = [
if is_duplicate { "DUP" } else { None },
if is_supplementary { "SUPP" } else { None },
if failed_qc { "FAIL" } else { None }
] |> filter(|f| f != None)
Given / Otherwise
The given expression provides declarative conditional chains —
cleaner than nested if/else for multi-criteria decisions:
let classification = given {
gc_content(seq) > 0.6 => "high_gc"
gc_content(seq) > 0.4 => "normal_gc"
gc_content(seq) > 0.2 => "low_gc"
otherwise => "at_rich"
}
# Sample QC decision
let qc_status = given {
mean_qual < 20 => "fail"
adapter_rate > 0.3 => "fail"
duplication_rate > 0.5 => "warn"
otherwise => "pass"
}
For / Else
The else block on a for loop runs only when the
loop completes without break — useful for search-and-fail patterns:
# Search for a specific motif
for seq in sequences {
if find_motif(seq, "TATAAA") != None {
print(f"Found TATA box in {seq.id}")
break
}
} else {
print("No TATA box found in any sequence")
}