Interactive REPL

The BioLang REPL (Read-Eval-Print Loop) is an interactive environment for exploratory data analysis, testing code snippets, and learning the language. Launch it with bl repl. It features syntax highlighting, tab completion, multi-line editing, persistent history, and a suite of introspection commands.

Getting Started

# Launch the REPL
bl repl
BioLang v0.1.0 REPL — Type :help for commands, Ctrl+D to exit
>>> let seq = dna"ATCGATCG"
=> dna"ATCGATCG"

>>> seq |> gc_content()
=> 0.5

>>> seq |> reverse_complement()
=> dna"CGATCGAT"

The REPL evaluates each line and prints the result. Variables persist across lines within a session. The last expression's value is also available as _ (underscore).

REPL Commands

Commands start with : and provide environment introspection and session management:

Command Description
:helpShow all available commands
:envList all variables in the current environment with their types
:type <expr>Show the type of an expression without evaluating it
:time <expr>Evaluate and print execution time
:profile <expr>Run expression with profiling (call counts, hot paths)
:load <file>Load and execute a .bl file into the current environment
:save <file>Save the current session's input history to a .bl file
:resetClear all variables and reset the environment
:pluginsList loaded plugins
:clearClear the screen
:quitExit the REPL (also Ctrl+D)

:env — Environment Inspection

>>> let seq = dna"ATCGATCG"
>>> let gc = seq |> gc_content()
>>> let reads = fastq("sample.fq.gz")

>>> :env
Name     Type       Value
seq      DNA        dna"ATCGATCG"
gc       Float      0.5
reads    Stream     Stream<FastqRecord> (unconsumed)

:type — Type Inspection

>>> :type dna"ATCG"
DNA

>>> :type dna"ATCG" |> gc_content()
Float

>>> :type [1, 2, 3] |> map(|x| x * 2)
List<Int>

>>> :type fasta("genome.fa")
Stream<FastaRecord>

:time — Benchmarking

>>> :time dna"ATCGATCG" * 1000000 |> gc_content()
=> 0.5
Time: 12.3ms

>>> :time fastq("big.fq.gz") |> map(|r| r.seq |> gc_content()) |> mean()
=> 0.423
Time: 3.41s

:profile — Profiling

>>> :profile fasta("genome.fa") |> map(|r| r.seq |> kmer_count(21)) |> collect()

Profile Results:
  kmer_count      called 24x    total: 890ms   avg: 37.1ms
  map             called 1x     total: 891ms
  fasta      called 1x     total: 45ms
  collect         called 1x     total: 936ms

Total: 936ms

Tab Completion

The REPL provides context-aware tab completion for variables, functions, methods, file paths, and command arguments:

# Variable names
>>> se<TAB>
seq    sequence    sequences

# Method completion on typed values
>>> seq |> gc_<TAB>
gc_content

# Builtin function completion
>>> ncbi_<TAB>
ncbi_search    ncbi_fetch    ncbi_gene    ncbi_pubmed    ncbi_sequence

# File path completion (after quotes)
>>> fasta("data/<TAB>
data/genome.fa    data/reads.fa    data/reference.fa.gz

# Command completion
>>> :<TAB>
:help  :env  :type  :time  :profile  :load  :save  :reset  :plugins  :clear  :quit

Multi-Line Input

The REPL automatically detects incomplete expressions and continues on the next line. Open braces, brackets, and parentheses trigger multi-line mode. You can also explicitly enter multi-line mode:

# Automatic multi-line detection
>>> let results = [1, 2, 3]
...   |> map(|x| {
...     value: x,
...     squared: x * x
...   })
...   |> to_table()
=> Table(3 rows x 2 cols)

# Pipe chains continue automatically
>>> dna"ATCGATCG"
...   |> reverse_complement()
...   |> transcribe()
...   |> translate()
=> protein"RI"

History

The REPL maintains persistent history across sessions, stored in ~/.biolang/history:

  • Up/Down arrows — navigate through previous inputs
  • Ctrl+R — reverse search through history
  • :save session.bl — save current session inputs to a file
# Reverse search
>>> <Ctrl+R>
(reverse-i-search)`kmer': seq |> kmer_count(21)

# Save your exploration as a script
>>> :save my_analysis.bl
Saved 42 inputs to my_analysis.bl

Rich Output

The REPL formats different value types with appropriate visual representations:

# DNA sequences show with color coding
>>> dna"ATCGATCG"
=> dna"ATCGATCG"

# Tables render with borders and alignment
>>> csv("data.csv") |> head(3)
=> ┌────────┬───────┬──────────┐
   │ sample │ gene  │ expr     │
   ├────────┼───────┼──────────┤
   │ S001   │ BRCA1 │ 12.45    │
   │ S002   │ BRCA1 │ 8.91     │
   │ S003   │ BRCA1 │ 15.23    │
   └────────┴───────┴──────────┘
   3 rows x 3 columns

# Maps show key-value pairs
>>> dna"ATCGATCG" |> kmer_count(2)
=> { "AT": 2, "TC": 2, "CG": 2, "GA": 1, "TG": 0, ... }

# Streams show type information
>>> fastq("reads.fq.gz")
=> Stream<FastqRecord> (unconsumed)

Loading Scripts

Use :load to execute a script and bring its definitions into scope. This is useful for setting up helper functions or loading data:

# Load a utility script
>>> :load utils.bl
Loaded 5 functions, 2 variables from utils.bl

# Now use the loaded functions
>>> my_custom_filter(data)

# Load fresh data
>>> :load setup.bl
>>> :env
Name       Type         Value
samples    Table        Table(100 rows x 8 cols)
metadata   Table        Table(100 rows x 3 cols)
config     Map          { genome: "hg38", ... }

REPL Tips

  • Use _ to reference the last result: _ |> len()
  • Prefix an expression with ; to suppress output: ;let data = csv("big_file.csv")
  • Use :time before committing to a full analysis to estimate runtime on a subset of data
  • Save exploratory sessions with :save to convert them into reproducible scripts