Implementation: src/hql/transpiler/syntax/function.ts, src/hql/transpiler/pipeline/transform/async-generators.ts
See
spec.mdfor the definitive reference on function parameter syntax.
HQL provides function support with these features:
fnfn (used in REPL memory persistence)=> syntax with $0, $1, $2 params[x = 10]& rest){key: default} syntax_ to use defaults (positional only)<T> syntax on function namesfn* with yield/yield*async fn* for async iterationAll functions compile to JavaScript functions with ES6+ support.
HQL supports both Lisp-style and JSON-style syntax for map parameters and data literals:
// Map parameters - unquoted keys, no commas
(fn connect {host: "localhost" port: 8080 ssl: false}
(+ (if ssl "https" "http") "://" host ":" port))
// Map call - unquoted keys
(connect {host: "api.example.com" ssl: true})
// Arrays - no commas
[1 2 3 4 5]
// Hash-maps - unquoted keys, no commas
{name: "Alice" age: 25 city: "NYC"}
// Map parameters - quoted keys, commas
(fn connect {"host": "localhost", "port": 8080, "ssl": false}
(+ (if ssl "https" "http") "://" host ":" port))
// Map call - quoted keys, commas
(connect {"host": "api.example.com", "ssl": true})
// Arrays - commas
[1, 2, 3, 4, 5]
// Hash-maps - quoted keys, commas
{"name": "Alice", "age": 25, "city": "NYC"}
Both styles compile to identical JavaScript.
// Named function
(fn add [a b]
(+ a b))
(add 3 5) // => 8
// Anonymous function
(let square (fn [x] (* x x)))
(square 5) // => 25
// No parameters
(fn get-value []
42)
// Single parameter
(fn double [x]
(* x 2))
defn is an alias for fn, primarily used for REPL memory persistence:
(defn add [a b]
(+ a b))
// Equivalent to:
(fn add [a b]
(+ a b))
=>)Concise arrow lambda syntax with Swift-style $N parameters:
// Implicit parameters ($0, $1, $2...)
(let double (=> (* $0 2)))
(double 5) // => 10
(let add (=> (+ $0 $1)))
(add 3 7) // => 10
// With map/filter/reduce
(map (=> (* $0 2)) [1 2 3 4 5]) // => [2 4 6 8 10]
(filter (=> (> $0 5)) [1 3 6 8 2 9]) // => [6 8 9]
(reduce (=> (+ $0 $1)) 0 [1 2 3 4 5]) // => 15
// Member access
(let users [{name: "Alice"}, {name: "Bob"}])
(map (=> ($0.name)) users) // => ["Alice", "Bob"]
// Explicit parameters (both bracket and paren syntax work)
(let square (=> [x] (* x x)))
(square 7) // => 49
(let multiply (=> (x y) (* x y)))
(multiply 6 7) // => 42
// Zero parameters
(let get-value (=> () 42))
(get-value) // => 42
Default values use = syntax inside positional parameter lists:
(fn multiply [x = 10 y = 20]
(* x y))
(multiply) // => 200 (10 * 20)
(multiply 5) // => 100 (5 * 20)
(multiply 5 3) // => 15 (5 * 3)
(multiply _ 7) // => 70 (10 * 7) - placeholder skips to default
For config-style functions with many parameters, use map syntax. All keys must have defaults:
(fn connect {host: "localhost" port: 8080 ssl: false}
(if ssl
(+ "https://" host ":" port)
(+ "http://" host ":" port)))
// Call with all defaults
(connect) // => "http://localhost:8080"
// Override specific keys
(connect {host: "api.example.com" ssl: true port: 443})
// => "https://api.example.com:443"
// Partial override
(connect {port: 3000}) // => "http://localhost:3000"
// JSON style also works
(connect {"host": "api.example.com", "ssl": true, "port": 443})
// Rest only
(fn sum [& rest]
(.reduce rest (fn [acc val] (+ acc val)) 0))
(sum 1 2 3 4 5) // => 15
// With regular params
(fn sum [x y & rest]
(+ x y (.reduce rest (fn [acc val] (+ acc val)) 0)))
(sum 10 20 1 2 3) // => 36
Use _ to skip arguments and use their default values (positional params with defaults only):
(fn calc [a = 1 b = 2 c = 3 d = 4]
(+ a b c d))
(calc _ _ 30 _) // => 37 (a=1, b=2, c=30, d=4)
(calc _ _ _ _) // => 10 (all defaults: 1 + 2 + 3 + 4)
Define different implementations based on argument count:
// Named multi-arity
(fn greet
([] "Hello!")
([name] (+ "Hello, " name "!"))
([greeting name] (+ greeting ", " name "!")))
(greet) // => "Hello!"
(greet "Alice") // => "Hello, Alice!"
(greet "Hi" "Bob") // => "Hi, Bob!"
// Anonymous multi-arity
(let handler (fn
([] "no args")
([x] (+ "one: " x))
([x y] (+ "two: " x " " y))))
Multi-arity functions compile to a switch on arguments.length. A rest parameter clause becomes the default case. If no arity matches and there is no rest clause, an error is thrown.
Array and object destructuring patterns work in parameter lists:
(fn process [[a b] c]
(+ a b c))
(fn swap [[x y]]
[y x])
Parameters and return types support TypeScript-style annotations:
// Parameter type annotation (no space after colon)
(fn add [a:number b:number]
(+ a b))
// Return type annotation on function name
(fn add:number [a b]
(+ a b))
// Return type annotation after parameter list
(fn add [a b] :number
(+ a b))
// Generic type parameters
(fn identity<T> [x:T] :T
x)
// Implicit return (last expression)
(fn double [x]
(* x 2))
// Explicit return
(fn double [x]
(return (* x 2)))
// Early return
(fn safe-divide [a b]
(if (=== b 0)
(return 0)
(/ a b)))
// Multiple return paths
(fn classify [x]
(cond
((< x 0) (return "negative"))
((=== x 0) (return "zero"))
((> x 0) (return "positive"))))
// Capturing outer variable
(let x 10)
(fn add-x [n]
(+ n x))
(add-x 5) // => 15
// Closure with state
(fn make-counter []
(var count 0)
(fn []
(= count (+ count 1))
count))
(var counter (make-counter))
(counter) // => 1
(counter) // => 2
// Function returning function
(fn make-adder [n]
(fn [x] (+ x n)))
(let add5 (make-adder 5))
(add5 10) // => 15
// Function as argument
(fn apply-twice [f x]
(f (f x)))
(fn add-one [n] (+ n 1))
(apply-twice add-one 5) // => 7
(fn factorial [n]
(if (<= n 1)
1
(* n (factorial (- n 1)))))
(factorial 5) // => 120
fn*)Generator functions produce iterators using yield:
// Named generator
(fn* range [start end]
(var i start)
(while (< i end)
(yield i)
(= i (+ i 1))))
// Anonymous generator
(fn* []
(yield 1)
(yield 2)
(yield 3))
// Yield without value
(fn* simple []
(yield)
(yield 42))
// yield* delegates to another iterator
(fn* combined []
(yield* [1 2 3])
(yield 4))
// Infinite sequence
(fn* fibonacci []
(var a 0)
(var b 1)
(while true
(yield a)
(var temp b)
(= b (+ a b))
(= a temp)))
// Async named function
(async fn fetch-data [url]
(let response (await (js/fetch url)))
(await (.json response)))
// Async anonymous function
(let fetcher (async fn [url] (await (js/fetch url))))
// Async with map params
(async fn fetch-with-options {url: "" timeout: 5000}
(await (js/fetch url)))
async fn*)Combine async/await with generators:
// Named async generator
(async fn* fetchPages [urls]
(for-of [url urls]
(yield (await (fetch url)))))
// Async generator with pagination
(async fn* paginate [startPage maxPages]
(var page startPage)
(while (<= page maxPages)
(const data (await (fetchPage page)))
(yield data)
(= page (+ page 1))))
// Anonymous async generator
(async fn* []
(yield (await (Promise.resolve 1)))
(yield (await (Promise.resolve 2))))
HQL:
(fn add [a b]
(+ a b))
Compiled JavaScript:
function add(a, b) {
return a + b;
}
HQL:
(let square (fn [x] (* x x)))
Compiled:
const square = (x) => x * x;
Anonymous functions that reference this compile to regular function expressions instead of arrow functions.
HQL:
(fn multiply {"x": 10, "y": 20}
(* x y))
Compiled:
function multiply({ x = 10, y = 20 } = {}) {
return x * y;
}
HQL:
(fn sum [x y & rest]
(+ x y (.reduce rest (fn [acc val] (+ acc val)) 0)))
Compiled:
function sum(x, y, ...rest) {
return x + y + rest.reduce((acc, val) => acc + val, 0);
}
HQL:
(fn greet
([] "Hello!")
([name] (+ "Hello, " name "!")))
Compiled:
function greet(...__args) {
switch (__args.length) {
case 0: {
return "Hello!";
}
case 1: {
const name = __args[0];
return "Hello, " + name + "!";
}
default:
throw new Error("No matching arity for function 'greet' with " + __args.length + " arguments");
}
}
HQL:
(fn* range [start end]
(var i start)
(while (< i end)
(yield i)
(= i (+ i 1))))
Compiled:
function* range(start, end) {
let i = start;
while (i < end) {
yield i;
i = i + 1;
}
}
HQL:
(async fn* fetchItems [urls]
(for-of [url urls]
(yield (await (fetch url)))))
Compiled:
async function* fetchItems(urls) {
for (const url of urls) {
yield await fetch(url);
}
}
() and []HQL functions provide:
fn (and defn alias)=> with $0, $1, $2 params)[x = 10 y = 20] with _ placeholders){key: default})& rest, like JavaScript ...args):type on params and return)<T> on function names)fn* with yield/yield*)async fn)async fn*)