Modules & Imports

BioLang's module system lets you split code across .bl files and load them with import. All top-level bindings in a file are exported automatically — there is no pub keyword.

Note: Import and file operations require the CLI (bl run). The examples on this page are not runnable in the browser playground.

import “path”

The basic import statement loads and executes a .bl file. The file's top-level bindings become available in the current scope:

# Load a file — all its top-level let/fn bindings are imported
import "helpers.bl"

# With an alias — access bindings as qc.check_quality(), qc.VERSION, etc.
import "lib/qc.bl" as qc

let result = qc.check_quality(reads)
println(f"Pass rate: {result.pass_rate}")

from “path” import names

To pull specific names into scope without an alias, use the from ... import form:

# Import only the bindings you need
from "lib/qc.bl" import check_quality, check_contamination

let qc = check_quality(reads)
println(f"Fail rate: {qc.fail_rate}")

Module Caching

Each module is loaded and evaluated once, then cached by its canonical file path. Subsequent imports of the same file (from any location) reuse the cached result:

# a.bl
import "shared.bl"           # loads and caches shared.bl

# b.bl
import "shared.bl"           # reuses the cached module — not re-executed

# Circular imports are detected and rejected at load time.

What Gets Exported

All top-level let and fn bindings in a file are visible to importers. There is no pub / private distinction. To keep something internal, put it inside a function rather than at the top level:

# File: qc.bl

# These are exported (top-level):
let VERSION = "1.0.0"

fn check_quality(reads, min_q) {
  # This helper is NOT exported — it's defined inside a function
  let compute_threshold = |scores| mean(scores) - 2.0 * stdev(scores)
  let threshold = compute_threshold(reads |> map(|r| r.quality))
  reads |> filter(|r| r.quality >= threshold)
}

Plugin System

When a path does not resolve to a file, BioLang looks for a matching plugin in ~/.biolang/plugins/. Plugins follow a subprocess JSON protocol and can be written in Python, R, TypeScript, or as native binaries:

# Install a plugin from the CLI:
#   $ bl add blast

# Then import it like a regular module:
import "blast" as blast

let hits = blast.blastn(query, db, evalue: 1e-10)

Project Structure

A typical BioLang project organizes modules by domain:

my-analysis/
  main.bl              # Entry point (bl run main.bl)
  lib/
    qc.bl              # Quality control functions
    alignment.bl       # Alignment helpers
    variant.bl         # Variant calling utilities
  data/
    samples.csv
  results/
# main.bl
import "lib/qc.bl" as qc
import "lib/alignment.bl" as align
from "lib/variant.bl" import call_and_filter

let samples = read_csv("data/sample_sheet.csv")

for sample in samples {
  let quality = qc.check(sample)
  let bam = align.run(sample)
  let variants = call_and_filter(bam)
  println(f"{sample.id}: {len(variants)} variants found")
}