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")
}