HQL is a Lisp-shaped language that compiles to JavaScript and TypeScript. It is designed to keep Lisp's small syntax while staying close to the JS ecosystem.
Create hello.hql:
(fn greet [name: string = "World"]
(str "Hello, " name "!"))
(print (greet name: "Ada"))
Run it:
hql run hello.hql
# Hello, Ada!
For an interactive session, start HLVM and type HQL expressions:
hlvm
One rule explains most syntax: parentheses are code; brackets and braces are data; separators choose the data lane.
| Form | Meaning |
|---|---|
(...) | Code form or quoted code-data list |
[1 2 3] | HQL persistent vector |
[1, 2, 3] | JavaScript array |
{:name "Ada"} | HQL persistent map |
{name: "Ada"} | JavaScript object |
#[1 2 3] | HQL persistent set |
#[1, 2, 3] | JavaScript Set |
(+ 1 2 3) ; code
[1 2 3] ; persistent vector
[1, 2, 3] ; JS array
{:name "Ada"} ; persistent map
{name: "Ada"} ; JS object
Commas are rejected in code forms. Spaces vs commas matter only in bracketed data forms.
(let nums [1 2 3]) ; immutable binding
(var count 0) ; reassignable binding
(def answer 42) ; module-level definition
(let [head ...tail] [1, 2, 3]) ; destructuring
(let {name: n, age: a} {name: "Ada", age: 37})
Use let for normal values. Use var only when the binding must be reassigned.
(fn add [a b]
(+ a b))
(fn connect [host: string = "localhost" port: number = 8080] -> string
(str host ":" port))
(connect)
(connect host: "api.com" port: 443)
Named arguments work for known lexical HQL function declarations. Unknown, imported, host, rest-parameter, and pattern-parameter calls stay positional.
Anonymous functions:
(let double (fn [x] (* x 2)))
(let triple (=> [x] (* x 3)))
defn is an alias for named fn.
(let x 0)
(let ready? true)
(let value 42)
(if (> x 0) "positive" "other")
(cond
((< x 0) "negative")
((zero? x) "zero")
(else "positive"))
(when ready?
(print "go"))
(match value
(case 0 "zero")
(case 42 "the answer")
(default "other"))
HQL is expression-oriented, so control-flow forms can return values.
(for [i 3]
(print i))
(for [i from: 0 to: 10 by: 2]
(print i))
(for-of [n [1, 2, 3]]
(print (* n 2)))
(loop [i 0 total 0]
(if (< i 5)
(recur (+ i 1) (+ total i))
total))
Use loop and recur when the loop state should stay explicit and
tail-recursive.
(import [parse stringify] from "@hql/json")
(parse "{\"ok\":true}")
(stringify {ok: true})
HQL imports JavaScript and TypeScript modules directly. Raw .hql source should
go through the HQL toolchain; plain JS runtimes consume compiled ESM output.
See Packages.
(new js/Date)
(js-call JSON "parse" "{\"ok\":true}")
(let items [1, 2])
(.push items 3)
items.length
Use comma-separated arrays and JS-object colons when a JavaScript API expects native JS data. Use persistent collections for HQL-first data.
See JavaScript Interop.
(take 3 (range 10))
(map inc [1 2 3])
(assoc {:name "Ada"} :role "engineer")
(str-join ", " ["a" "b" "c"])
The standard library is generated from source and includes runtime-verified examples.
hql init ./my-lib --name @example/my-lib --version 0.1.0
hql check ./my-lib
hql pack ./my-lib --target all
hql new ./site --web --yes
hql dev ./site --dry-run
hql new ./app --mobile --yes
hql run ios ./app --dry-run
See CLI, Packages, and Browser, Web, And Mobile.
(+ 1, 2) is invalid. Use spaces.[1] to be a JS array. It is a persistent vector; use [1,].{name: "Ada"} when you wanted a persistent map. Use {:name "Ada"}..hql files in a plain JS runtime without compiling first.