Variables & Assignment

BioLang uses let to declare variables. All variables are dynamically typed — there are no type annotations. Variables can be reassigned freely after declaration.

Declaring Variables

Use let to bind a value to a name:

let name = "BRCA1"
let count = 42
let ratio = 0.95
let active = true

println(f"Gene: {name}, count: {count}")
println(f"Ratio: {ratio}, active: {active}")

Reassignment

After a variable is declared with let, you can reassign it without let:

let x = 10
println(f"Before: {x}")

x = 20
println(f"After: {x}")

# Reassignment can change the type
x = "now I'm a string"
println(f"Changed type: {x} (type: {typeof(x)})")

All Value Types

Variables can hold any BioLang value. The type is determined by the value, not a declaration:

# Primitives
let n = 42
let pi = 3.14159
let msg = "hello"
let flag = true

# Bio literals
let seq = dna"ATCGATCG"
let rna_seq = rna"AUCGAUCG"
let prot = protein"MKTLLILAVS"

# Collections
let nums = [1, 2, 3, 4, 5]
let info = {gene: "BRCA1", chrom: "chr17", score: 0.95}

println(f"DNA: {seq} ({typeof(seq)})")
println(f"List: {nums} ({typeof(nums)})")
println(f"Record: {info} ({typeof(info)})")

String Interpolation

Use f"..." strings to embed expressions inside curly braces:

let gene = "TP53"
let pos = 7687550
let score = 0.987

println(f"Gene {gene} at position {pos}")
println(f"Score: {score}, rounded: {int(score * 100)}%")

# Expressions work inside braces
let nums = [10, 20, 30]
println(f"Sum is {sum(nums)}, mean is {mean(nums)}")

Shadowing

You can re-declare a variable with let in the same scope. The new binding shadows the old one:

let x = 10
println(f"Original: {x}")

let x = x * 2 + 5
println(f"Shadowed: {x}")

# Shadowing can change type
let val = "42"
println(f"String: {val} ({typeof(val)})")
let val = int(val)
println(f"Int: {val} ({typeof(val)})")

Scope Rules

Variables are block-scoped. A variable declared inside {} is not visible outside:

let outer = "I'm outer"

let result = {
  let inner = 100
  inner + 50
}

println(f"Result: {result}")
println(f"Outer: {outer}")
# inner is not accessible here

Accumulation Patterns

A common pattern is reassigning a variable inside a loop:

let genes = ["BRCA1", "TP53", "EGFR", "MYC"]
let total = 0
let long_names = []

for g in genes {
  total = total + 1
  if len(g) >= 4 {
    long_names = push(long_names, g)
  }
}

println(f"Total: {total}")
println(f"Long names: {long_names}")

Record Spread

The spread operator ... merges records. Later fields override earlier ones:

let defaults = {genome: "GRCh38", min_qual: 30, min_depth: 10}
let config = {...defaults, sample_id: "S001", min_qual: 20}

println(f"Genome: {config.genome}")
println(f"Min qual (overridden): {config.min_qual}")
println(f"Sample: {config.sample_id}")

List Concatenation

Use ++ to concatenate lists or strings:

let a = [1, 2, 3]
let b = [4, 5, 6]
let combined = a ++ b
println(f"Combined: {combined}")

let greeting = "Hello" ++ " " ++ "World"
println(greeting)

Destructuring

BioLang does not support destructuring assignment. To extract values from a record or list, access fields or indices directly:

let info = {gene: "BRCA1", pos: 43044295, score: 0.99}

# Access fields individually
let gene = info.gene
let pos = info.pos
println(f"{gene} at {pos}")

# Access list elements by index
let items = ["first", "second", "third"]
let first = items[0]
println(f"First item: {first}")

No Type Annotations

BioLang is dynamically typed. There are no type annotations on variables, function parameters, or return types. Use typeof() to inspect types at runtime:

let x = 42
let s = "hello"
let seq = dna"ATCG"

println(f"{x} is {typeof(x)}")
println(f"{s} is {typeof(s)}")
println(f"{seq} is {typeof(seq)}")