Functions & Closures
Functions in BioLang are first-class values. They can be assigned to variables, passed as arguments, and returned from other functions. BioLang supports named function declarations, anonymous lambdas, and closures that capture their environment.
Function Declarations
Functions are declared with the fn keyword. The return value is the
last expression in the body — no explicit return needed:
# Simple function
fn greet(name) {
println(f"Hello, {name}!")
}
greet("BRCA1") # Hello, BRCA1!
# The last expression is the return value
fn add(a, b) {
a + b
}
println(add(3, 4)) # 7
# Multi-statement function
fn describe_seq(seq) {
let gc = gc_content(seq)
let size = len(seq)
f"Length={size}, GC={gc}"
}
println(describe_seq(dna"ATCGATCG"))
Implicit Return
The last expression in a function body is its return value. No return
keyword is needed unless you want an early exit:
fn square(x) { x * x }
fn cube(x) { x * x * x }
println(square(5)) # 25
println(cube(3)) # 27
fn average(values) {
sum(values) / len(values)
}
println(average([10, 20, 30])) # 20
Lambda Expressions
Lambdas are anonymous functions written with pipe-delimited parameters. They are used
extensively with higher-order functions like map, filter,
and reduce:
# Single-parameter lambda
let double = |x| x * 2
println(double(21)) # 42
# Multi-parameter lambda
let add = |a, b| a + b
println(add(3, 4)) # 7
# Lambdas in pipe chains
let results = [10, 25, 50, 75, 90]
|> map(|s| s / 100.0)
|> filter(|s| s >= 0.5)
println(results) # [0.5, 0.75, 0.9]
Multi-line Lambdas
For lambdas that need more than one expression, use a block body with braces:
let classify = |score| {
if score >= 90 { "excellent" }
else if score >= 70 { "good" }
else { "needs work" }
}
println(classify(95)) # excellent
println(classify(75)) # good
println(classify(50)) # needs work
# Multi-line lambda in a pipe
let data = [1, 2, 3, 4, 5]
let result = data |> map(|x| {
let squared = x * x
let label = if squared > 10 then "big" else "small"
f"{x}^2={squared} ({label})"
})
each(result, |r| println(r))
Closures
Lambdas capture variables from their enclosing scope, forming closures:
# Closure captures 'threshold'
fn make_filter(threshold) {
|value| value >= threshold
}
let above_50 = make_filter(50)
let above_80 = make_filter(80)
let scores = [30, 55, 72, 88, 95]
println(scores |> filter(above_50)) # [55, 72, 88, 95]
println(scores |> filter(above_80)) # [88, 95]
# Closure captures 'multiplier'
fn make_scaler(multiplier) {
|x| x * multiplier
}
let triple = make_scaler(3)
println([1, 2, 3] |> map(triple)) # [3, 6, 9]
Recursion
Named functions can call themselves recursively:
# Factorial
fn factorial(n) {
if n <= 1 then 1
else n * factorial(n - 1)
}
println(factorial(10)) # 3628800
# Fibonacci
fn fib(n) {
if n <= 1 then n
else fib(n - 1) + fib(n - 2)
}
println(fib(10)) # 55
# GCD (Euclidean algorithm)
fn gcd(a, b) {
if b == 0 then a
else gcd(b, a % b)
}
println(gcd(48, 18)) # 6
Early Return
Use return for explicit early returns when you need to bail out
before the end of the function:
fn find_first_long(items, min_len) {
for item in items {
if len(item) >= min_len {
return item
}
}
return "none found"
}
let genes = ["TP53", "BRCA1", "A", "EGFR"]
println(find_first_long(genes, 4)) # BRCA1
Functions as Values
Functions are first-class: you can store them in variables, lists, and records, and pass them as arguments:
# Store functions in variables
let ops = [|x| x + 1, |x| x * 2, |x| x * x]
# Apply each operation to a value
let val = 3
for op in ops {
println(op(val))
}
# 4
# 6
# 9
# Pass functions as arguments
fn apply_twice(f, x) {
f(f(x))
}
println(apply_twice(|x| x + 10, 5)) # 25
println(apply_twice(|x| x * 2, 3)) # 12
Functions with Pipes
Functions work naturally with the pipe operator. The data flows left to right:
# Define reusable pipeline stages
fn keep_long(items, min_len) {
items |> filter(|x| len(x) >= min_len)
}
fn to_upper_list(items) {
items |> map(|x| upper(x))
}
# Compose into a pipeline
let genes = ["brca1", "tp53", "a", "egfr", "ab"]
let result = genes
|> keep_long(3)
|> to_upper_list()
|> sort()
println(result) # [BRCA1, EGFR, TP53]
Higher-Order Patterns
# compose: combine two functions into one
fn compose(f, g) {
|x| f(g(x))
}
let shout = compose(upper, trim)
println(shout(" hello ")) # HELLO
# Apply a function n times
fn apply_n(f, x, n) {
let result = x
for i in range(n) {
result = f(result)
}
result
}
println(apply_n(|x| x * 2, 1, 10)) # 1024