Type System

BioLang is dynamically typed. Every value carries its type at runtime, and you never write type annotations. Use typeof() to inspect a value's type.

Primitive Types

Int

64-bit signed integers. Underscores can be used as visual separators:

let count = 42
let big = 1_000_000
let negative = -17

println(f"count={count}, big={big}, negative={negative}")
println(f"Type: {typeof(count)}")

Float

64-bit IEEE 754 floating-point numbers:

let pi = 3.14159
let tiny = 1.0e-10

println(f"pi={pi}")
println(f"tiny={tiny}")
println(f"Type: {typeof(pi)}")

Str

UTF-8 strings with f-string interpolation:

let name = "BioLang"
let greeting = f"Hello, {name}!"
println(greeting)

# String operations
println(upper(name))
println(substr(name, 0, 3))
println(split("A,B,C", ","))
println(f"Type: {typeof(name)}")

Bool

let yes = true
let no = false
let result = 5 > 3
let combined = yes && !no

println(f"yes={yes}, no={no}, 5>3={result}, combined={combined}")
println(f"Type: {typeof(yes)}")

Nil

nil represents the absence of a value:

let nothing = nil
println(f"Value: {nothing}")
println(f"Type: {typeof(nothing)}")

# Use ?? for nil coalescing
let safe = nothing ?? "default"
println(f"Safe: {safe}")

Biology Types

DNA

DNA sequences with literal syntax. Valid nucleotides: A, T, C, G, N.

let seq = dna"ATCGATCGATCG"
println(f"Sequence: {seq}")
println(f"Type: {typeof(seq)}")
println(f"Length: {seq_len(seq)} bp")
println(f"GC content: {gc_content(seq)}")

let rc = reverse_complement(seq)
println(f"Reverse complement: {rc}")

let counts = base_counts(seq)
println(f"Base counts: {counts}")

RNA

RNA sequences (A, U, C, G, N). Created via rna"" literals or by transcribing DNA:

let mrna = rna"AUGCGAUUCGAA"
println(f"RNA: {mrna}")
println(f"Type: {typeof(mrna)}")

let protein = translate(mrna)
println(f"Protein: {protein}")

# Transcribe DNA to RNA
let dna_seq = dna"ATGCGATTCGAA"
let rna_from_dna = transcribe(dna_seq)
println(f"Transcribed: {rna_from_dna}")

Protein

Amino acid sequences using standard one-letter codes:

let prot = protein"MKTLLILAVS"
println(f"Protein: {prot}")
println(f"Type: {typeof(prot)}")
println(f"Length: {seq_len(prot)} aa")

Collection Types

List

Ordered, heterogeneous collections:

let nums = [1, 2, 3, 4, 5]
let genes = ["BRCA1", "TP53", "EGFR"]

println(f"First: {nums[0]}")
println(f"Length: {len(nums)}")
println(f"Type: {typeof(nums)}")

# List operations
let doubled = nums |> map(|x| x * 2)
println(f"Doubled: {doubled}")

let evens = nums |> filter(|x| x % 2 == 0)
println(f"Evens: {evens}")

let total = sum(nums)
println(f"Sum: {total}")

Record

Named key-value pairs (like a dictionary with known keys):

let gene = {name: "BRCA1", chrom: "chr17", start: 43044295}

println(f"Gene: {gene.name}")
println(f"Chrom: {gene.chrom}")
println(f"Type: {typeof(gene)}")

# Records with string keys
let lookup = {"BRCA1": 672, "TP53": 7157}
println(f"BRCA1 ID: {lookup.BRCA1}")

# Spread to create modified copies
let updated = {...gene, score: 0.95, start: 43044300}
println(f"Updated: {updated}")

Table

Column-oriented tabular data with named columns:

let t = table(
  gene = ["BRCA1", "TP53", "EGFR"],
  score = [0.95, 0.87, 0.72],
  chrom = ["chr17", "chr17", "chr7"]
)

println(f"Type: {typeof(t)}")
println(f"Rows: {len(t)}, Cols: {ncols(t)}")
println(t)

Checking Types at Runtime

Use typeof() to get the type name as a string:

println(typeof(42))                  # Int
println(typeof(3.14))                # Float
println(typeof("hello"))             # Str
println(typeof(true))                # Bool
println(typeof(nil))                 # Nil
println(typeof(dna"ATCG"))           # DNA
println(typeof(rna"AUGC"))           # RNA
println(typeof(protein"MKT"))        # Protein
println(typeof([1, 2, 3]))           # List
println(typeof({x: 1}))             # Record

Type-checking predicates are also available:

println(f"is_int(42): {is_int(42)}")
println(f"is_str(42): {is_str(42)}")
println(f"is_dna(dna\"ATCG\"): {is_dna(dna\"ATCG\")}")
println(f"is_list([1]): {is_list([1])}")
println(f"is_nil(nil): {is_nil(nil)}")

Type Conversions

BioLang provides explicit conversion functions. There is no implicit coercion:

# String to number
let n = int("42")
let f = float("3.14")
println(f"int('42') = {n} ({typeof(n)})")
println(f"float('3.14') = {f} ({typeof(f)})")

# Number to string
let s = str(42)
println(f"str(42) = {s} ({typeof(s)})")

# Number conversions
let as_float = float(42)
let as_int = int(3.7)
println(f"float(42) = {as_float}")
println(f"int(3.7) = {as_int}")

Bio Conversions

Convert strings to biological sequence types with validation:

# String to bio types
let seq = dna("ATCGATCG")
let rna_seq = rna("AUCGAUCG")
let prot = protein("MKT")

println(f"DNA: {seq} ({typeof(seq)})")
println(f"RNA: {rna_seq} ({typeof(rna_seq)})")
println(f"Protein: {prot} ({typeof(prot)})")

# Bio transformations
let dna_seq = dna"ATGATGATG"
let as_rna = transcribe(dna_seq)
let as_protein = translate(as_rna)
println(f"{dna_seq} -> {as_rna} -> {as_protein}")

No Static Typing

BioLang has no static type system, no type annotations on variables or functions, and no generics. Everything is determined at runtime:

# Variables can hold any type
let x = 42
println(f"x = {x} ({typeof(x)})")

x = "now a string"
println(f"x = {x} ({typeof(x)})")

x = dna"ATCG"
println(f"x = {x} ({typeof(x)})")

# Functions accept any argument types
fn describe(val) {
  println(f"Value: {val}, Type: {typeof(val)}")
}

describe(42)
describe("hello")
describe([1, 2, 3])
describe(dna"ATCG")