Pattern Matching

BioLang's match expression tests a value against a series of patterns. It supports literal patterns, variable binding, wildcard _, guard clauses, or-patterns with |, and enum variant destructuring. match is an expression — it returns the value of the matching branch.

Basic Match

The first matching arm wins. Use _ as a catch-all:

# Match on string values
let base = "A"
let name = match base {
  "A" => "Adenine",
  "T" => "Thymine",
  "C" => "Cytosine",
  "G" => "Guanine",
  _ => "Unknown"
}
println(name)

Literal Patterns

Match against integers, floats, strings, and booleans:

# Integer patterns
let code = 2
let label = match code {
  1 => "low",
  2 => "medium",
  3 => "high",
  _ => "unknown"
}
println(f"Code {code} = {label}")

# Boolean patterns
let paired = true
let msg = match paired {
  true => "paired-end sequencing",
  false => "single-end sequencing"
}
println(msg)

Variable Binding

An identifier in a pattern binds the matched value to that name inside the arm:

# The variable 'x' captures the matched value
let val = 42
let result = match val {
  0 => "zero",
  x => f"got: {x}"
}
println(result)

Guard Clauses

Add if conditions after a pattern for fine-grained control. The guard is evaluated only when the pattern matches:

# Classify a score using guards
let classify = |score| match score {
  s if s >= 90 => "A",
  s if s >= 80 => "B",
  s if s >= 70 => "C",
  _ => "F"
}

[95, 82, 71, 55] |> each(|s| println(f"{s} => {classify(s)}"))

Or Patterns

Combine multiple patterns with | to share a branch:

# Multiple values map to the same result
let bases = ["A", "G", "C", "T", "U"]

bases |> each(|b| {
  let kind = match b {
    "A" | "G" => "purine",
    "C" | "T" | "U" => "pyrimidine",
    _ => "unknown"
  }
  println(f"{b} is a {kind}")
})

Enum Variant Patterns

Match on string values for classification:

let variant_type = "missense"

let impact = match variant_type {
  "frameshift" => "HIGH",
  "nonsense" => "HIGH",
  "missense" => "MODERATE",
  "synonymous" => "LOW",
  _ => "UNKNOWN"
}
println(f"Impact: {impact}")

Match as an Expression

Since match returns a value, it can be used anywhere an expression is expected:

# Use match in a map pipeline
let scores = [95, 72, 88, 45, 91]

let grades = scores |> map(|s| match s {
  s if s >= 90 => "A",
  s if s >= 80 => "B",
  s if s >= 70 => "C",
  _ => "F"
})

println(grades)

match with type()

Combine type() with match to dispatch on value types:

# Describe the type of a value
let describe = |x| match type(x) {
  "Int" => f"integer: {x}",
  "Float" => f"float: {x}",
  "String" => f"string: {x}",
  "List" => f"list with {len(x)} items",
  _ => f"other: {x}"
}

println(describe(42))
println(describe(3.14))
println(describe("hello"))
println(describe([1, 2, 3]))

Wildcard Pattern

The underscore _ matches any value without binding it. Always include a _ arm to handle unexpected cases:

# Catch-all for unrecognized input
let enzyme = |site| match site {
  "GAATTC" => "EcoRI",
  "AAGCTT" => "HindIII",
  "GGATCC" => "BamHI",
  _ => "unknown"
}

["GAATTC", "GGATCC", "AAAA"] |> each(|s| println(f"{s} => {enzyme(s)}"))

Nested Match

Match expressions can be nested or used inside pipe chains for complex branching:

# Classify and filter in a pipeline
let data = [
  {name: "TP53", impact: "HIGH"},
  {name: "BRCA1", impact: "MODERATE"},
  {name: "TTN", impact: "LOW"},
  {name: "EGFR", impact: "HIGH"},
]

let priorities = data |> map(|v| {
  let pri = match v.impact {
    "HIGH" => 3,
    "MODERATE" => 2,
    _ => 1
  }
  {name: v.name, priority: pri}
})

priorities
  |> filter(|p| p.priority >= 2)
  |> each(|p| println(f"{p.name}: priority {p.priority}"))