Control Flow

BioLang provides familiar control flow constructs — if/else, for loops, while loops, match expressions, and try/catch for error handling. A key distinction is that if/else are expressions that return values, not just statements.

If / Then / Else

BioLang supports two styles of conditionals: the inline if ... then ... else ... form and the block form with braces. Both are expressions that return values:

# Inline form with then/else
let x = 42
let label = if x > 50 then "big" else "small"
println(label)   # small

# Block form with braces
let score = 85
let grade = if score >= 90 {
  "A"
} else if score >= 80 {
  "B"
} else if score >= 70 {
  "C"
} else {
  "F"
}
println(grade)   # B

If Without Else

An if without an else branch executes conditionally. When used as a statement, no else is required:

# Statement usage — no else needed
let verbose = true
if verbose {
  println("Debug mode is on")
}

# Inline form
let n = 7
if n > 5 then println(f"{n} is greater than 5")

Conditional Chains

fn classify_gc(gc) {
  if gc > 0.6 { "high GC" }
  else if gc > 0.4 { "normal GC" }
  else if gc > 0.2 { "low GC" }
  else { "AT-rich" }
}

let seqs = [dna"GCGCGCGC", dna"ATCGATCG", dna"AAATTTAA"]
for seq in seqs {
  let gc = gc_content(seq)
  println(f"{seq}: {classify_gc(gc)}")
}

For Loops

The for loop iterates over any iterable value — lists, ranges, strings, and maps:

# Iterate over a list
let genes = ["BRCA1", "TP53", "EGFR"]
for gene in genes {
  if len(gene) <= 4 then println(f"{gene} is short")
  else println(f"{gene} is long")
}
# BRCA1 is long
# TP53 is short
# EGFR is short

# Iterate over a range
for i in range(5) {
  println(f"Index: {i}")
}

# Iterate with enumerate
let items = ["alpha", "beta", "gamma"]
for pair in enumerate(items) {
  println(f"{pair[0]}: {pair[1]}")
}
# 0: alpha
# 1: beta
# 2: gamma

Iterating Over Maps

let gene_scores = {"BRCA1": 0.95, "TP53": 0.87, "EGFR": 0.72}

# Iterate over keys
for gene in keys(gene_scores) {
  let score = gene_scores[gene]
  if score > 0.8 {
    println(f"{gene} is significant (score={score})")
  }
}

# Iterate over values
let total = 0.0
for score in values(gene_scores) {
  total = total + score
}
println(f"Total: {total}")

While Loops

# Basic while loop
let count = 0
let total = 0
while count < 10 {
  total = total + count
  count = count + 1
}
println(f"Sum 0..9 = {total}")   # Sum 0..9 = 45

# Collatz conjecture
let n = 27
let steps = 0
while n != 1 {
  if n % 2 == 0 then n = n / 2
  else n = n * 3 + 1
  steps = steps + 1
}
println(f"Collatz steps for 27: {steps}")   # Collatz steps for 27: 111

Break and Continue

# break exits the loop immediately
let nums = [2, 4, 6, 7, 8, 10]
for n in nums {
  if n % 2 != 0 {
    println(f"Found odd number: {n}")
    break
  }
  println(f"{n} is even")
}
# 2 is even
# 4 is even
# 6 is even
# Found odd number: 7

# continue skips to the next iteration
let result = []
for n in range(10) {
  if n % 3 == 0 { continue }
  result = push(result, n)
}
println(result)   # [1, 2, 4, 5, 7, 8]

Match Expressions

The match expression selects a branch based on the value being matched. Use _ as a wildcard to match anything:

# Match on values
fn codon_to_amino(codon) {
  match codon {
    "ATG" => "Met (Start)",
    "TAA" => "Stop",
    "TAG" => "Stop",
    "TGA" => "Stop",
    _ => "other"
  }
}
println(codon_to_amino("ATG"))   # Met (Start)
println(codon_to_amino("TAA"))   # Stop
println(codon_to_amino("GCT"))   # other

# Match as an expression
let base = "A"
let complement = match base {
  "A" => "T",
  "T" => "A",
  "G" => "C",
  "C" => "G",
  _ => "N"
}
println(f"Complement of {base} is {complement}")

Try / Catch

Use try/catch to handle errors gracefully:

# Basic error handling
let result = try {
  100 / 0
} catch e {
  println(f"Error: {e}")
  -1
}
println(f"Result: {result}")

# Try/catch in a pipeline
let values = [10, 0, 5, 0, 2]
let safe_reciprocals = values |> map(|v| {
  try { 1.0 / v } catch _ { 0.0 }
})
println(safe_reciprocals)   # [0.1, 0, 0.2, 0, 0.5]

Return

Use return for early exit from a function:

fn find_first(items, predicate) {
  for item in items {
    if predicate(item) {
      return item
    }
  }
  return nil
}

let nums = [3, 7, 12, 5, 18, 2]
let first_big = find_first(nums, |x| x > 10)
println(f"First > 10: {first_big}")   # First > 10: 12

Nested Loops

# Build a multiplication table
for i in range(1, 4) {
  let row = []
  for j in range(1, 4) {
    row = push(row, i * j)
  }
  println(f"{i}: {row}")
}
# 1: [1, 2, 3]
# 2: [2, 4, 6]
# 3: [3, 6, 9]

Functional vs. Imperative

BioLang supports both styles. Functional pipes are preferred for transforms; loops are clearer for side effects:

# Functional style (preferred for transforms)
let squares = [1, 2, 3, 4, 5] |> map(|x| x * x)
println(squares)   # [1, 4, 9, 16, 25]

let evens = [1, 2, 3, 4, 5, 6] |> filter(|x| x % 2 == 0)
println(evens)   # [2, 4, 6]

let total = [1, 2, 3, 4, 5] |> reduce(|a, b| a + b)
println(total)   # 15

# Loop style (clearer for side effects and complex logic)
let acc = 0
for val in [1, 2, 3, 4, 5] {
  acc = acc + val
}
println(f"Loop total: {acc}")   # Loop total: 15

Putting It All Together

# A complete example combining control flow constructs
let sequences = [
  {name: "seq1", bases: dna"GCGCGCGC"},
  {name: "seq2", bases: dna"ATATATAT"},
  {name: "seq3", bases: dna"ATCGATCG"},
  {name: "seq4", bases: dna"GGGGCCCC"}
]

for s in sequences {
  let gc = gc_content(s.bases)
  let category = if gc > 0.6 then "GC-rich"
    else if gc < 0.4 then "AT-rich"
    else "balanced"
  println(f"{s.name}: GC={gc}, {category}")
}

# Filter and summarize
let gc_rich = sequences
  |> filter(|s| gc_content(s.bases) > 0.5)
  |> map(|s| s.name)
println(f"GC-rich sequences: {gc_rich}")