A hybrid programming language with static typing, pattern matching, and built-in binary data support.
Download from Releases:
- macOS:
funxy-darwin-amd64orfunxy-darwin-arm64 - Linux:
funxy-linux-amd64orfunxy-linux-arm64 - Windows:
funxy-windows-amd64.exe
Each release also includes a -tree variant (e.g., funxy-darwin-arm64-tree) which uses the legacy Tree-Walk interpreter. This is slower but provides more detailed stack traces for debugging compiler issues.
mv funxy-darwin-arm64 funxy
./funxy hello.langgit clone https://github.com/funvibe/funxy
cd funxy
make build
./funxy hello.lang- Added
-cflag to compile source to .fbc bytecode files - Added
-rflag to run pre-compiled bytecode
./funxy -c hello.lang
./funxy -r hello.fbc- Bytecode compilation (-c) works for single-file programs
- Module imports are not yet supported in compiled bytecode
Running Tests:
# Run all tests (VM backend)
go test -v ./tests/...
# Run all tests (Tree-Walk backend)
go test -v ./tests/... -treeRequires Go 1.25+
# Run a program
./funxy hello.lang
# Run from stdin
echo 'print("Hello!")' | ./funxy
# Web playground
./funxy playground/playground.lang
# Open http://localhost:8080
# Show help
./funxy -help
./funxy -help lib/httpprint("Hello, Funxy!")Save as hello.lang and run: ./funxy hello.lang
import "lib/list" (map)
directive "strict_types" // Enforce stricter type checking
numbers = [1, 2, 3] // Type inferred: List<Int>
// Compact lambdas and implicit conversions (Int -> Float)
doubled = map(\x -> x * 2.0, numbers)
print(doubled) // [2.0, 4.0, 6.0]
// Explicit types when needed
fun add(a: Int, b: Int) -> Int { a + b }fun describe(n) {
match n {
0 -> "zero"
n if n < 0 -> "negative"
_ -> "positive"
}
}
// Record destructuring
user = { name: "admin", role: "superuser" }
match user {
{ name: "admin", role: r } -> print("Admin: ${r}")
_ -> print("Guest")
}fun route(method, path) {
match (method, path) {
("GET", "/users/{id}") -> "User: ${id}"
("GET", "/files/{...path}") -> "File: ${path}"
_ -> "Not found"
}
}
print(route("GET", "/users/42")) // User: 42
print(route("GET", "/files/css/main.css")) // File: css/main.cssimport "lib/list" (filter, map, foldl)
result = [1, 2, 3, 4, 5]
|> filter(\x -> x % 2 == 0) // Lambda syntax
|> map(\x -> x * x)
|> foldl(\acc, x -> acc + x, 0)
|> %"Result: %.2f" // Pipe formatting
// Result: 20.00type Shape = Circle Float | Rectangle Float Float
fun area(s: Shape) -> Float {
match s {
Circle r -> 3.14 * r * r
Rectangle w h -> w * h
}
}
print(area(Circle(5.0))) // 78.5
print(area(Rectangle(3.0, 4.0))) // 12.0Convenient syntax for record arguments in function calls:
type Config = { host: String, port: Int }
fun connect(config: Config) {
print("Connecting to ${config.host}:${config.port}")
}
// Call with shorthand - no braces needed!
connect(host: "localhost", port: 8080)
// Equivalent to: connect({ host: "localhost", port: 8080 })The record argument must be the last parameter, and you can mix regular arguments with named record fields.
fun createUser(name, options: { age: Int, active: Bool }) {
// ...
}
// Usage
createUser("Alice", age: 25, active: true)For cleaner DSL-style code, functions (lowercase identifiers) can be called with a block argument without parentheses:
import "kit/ui" (div, span, text, render)
// Clean syntax - no parentheses needed!
page = div {
span { text("Header") }
div {
text("Content line 1")
text("Content line 2")
}
span { text("Footer") }
}
html = render(page)
// <div><span>Header</span><div>Content line 1Content line 2</div><span>Footer</span></div>Monadic operations simplified:
fun maybeAdd(mx, my) {
do {
x <- mx
y <- my
pure(x + y)
}
}Expressive list generation:
chars = 'a'..'z'
squares = [x * x | x <- 1..10, x % 2 == 0]fun countdown(n, acc) {
if n == 0 { acc }
else { countdown(n - 1, acc + 1) }
}
print(countdown(1000000, 0)) // Works, no stack overflowFunxy includes a built-in debugger.
./funxy -debug script.langModules can import each other — the analyzer resolves cycles automatically:
// a/a.lang
package a (getB)
import "../b" as b
fun getB() -> b.BType { b.makeB() }
// b/b.lang
package b (BType, makeB)
import "../a" as a
type BType = { val: Int }
fun makeB() -> BType { { val: 1 } }
| Module | Description |
|---|---|
lib/list |
map, filter, foldl, sort, zip, range |
lib/string |
split, trim, replace, contains |
lib/map |
Key-value operations |
lib/tuple |
Tuple manipulation |
lib/option |
Option type utilities |
lib/result |
Result type utilities |
lib/json |
jsonEncode, jsonDecode |
lib/http |
HTTP client and server |
lib/ws |
WebSocket client and server |
lib/sql |
SQLite (built-in, no drivers needed) |
lib/bits |
Bit-level parsing (funbit) |
lib/bytes |
Byte manipulation |
lib/task |
async/await |
lib/crypto |
sha256, md5, base64, hmac |
lib/regex |
Regular expressions |
lib/io |
Files and directories |
lib/sys |
Args, env, exec |
lib/date |
Date and time |
lib/uuid |
UUID generation |
lib/math |
Math functions |
lib/bignum |
BigInt, Rational |
lib/char |
Character functions |
lib/test |
Unit testing |
lib/log |
Structured logging |
lib/csv |
CSV parsing and encoding |
lib/flag |
Command line flags |
Run ./funxy -help lib/<name> for documentation.
- reference — Reference Manual
- tutorial — Step-by-step tutorials
- playground — Run code in a browser
Supported: .lang, .funxy, .fx
All files in a package must use the same extension (determined by the main file).
import "lib/json" (jsonEncode)
users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
]
fun handler(method, path) {
match (method, path) {
("GET", "/api/users") -> {
status: 200,
body: jsonEncode(users)
}
("GET", "/api/users/{id}") -> {
status: 200,
body: jsonEncode({ userId: id })
}
_ -> { status: 404, body: "Not found" }
}
}import "lib/list" (filter)
fun qsort(xs) {
match xs {
[] -> []
[pivot, ...rest] -> {
less = filter(fun(x) { x < pivot }, rest)
greater = filter(fun(x) { x >= pivot }, rest)
qsort(less) ++ [pivot] ++ qsort(greater)
}
}
}
print(qsort([3, 1, 4, 1, 5, 9, 2, 6])) // [1, 1, 2, 3, 4, 5, 6, 9]import "lib/bits" (bitsExtract, bitsInt)
// TCP flags: 6 bits
packet = #b"010010" // SYN + ACK
specs = [
bitsInt("urg", 1), bitsInt("ack", 1),
bitsInt("psh", 1), bitsInt("rst", 1),
bitsInt("syn", 1), bitsInt("fin", 1)
]
match bitsExtract(packet, specs) {
Ok(flags) -> print(flags) // %{"ack" => 1, "syn" => 1, ...}
Fail(e) -> print(e)
}