DEV Community

Oleg
Oleg

Posted on • Edited on

I built a scripting language that compiles to self-contained binaries

Hey! I've been working on a scripting language called Funxy. It's written in Go, statically typed with inference (so you rarely write type annotations), and the main idea is painless distribution — your script compiles to a single binary you can scp and run.

I know there's no shortage of programming languages, so let me focus on what actually makes this different from writing the same thing in Python/Go/Rust.

The build story

The thing that motivated this whole project: I was tired of deploying scripts that need a runtime, virtualenvs, or a build toolchain on the target.

funxy build takes your script and produces a single binary — bytecode + VM + all dependencies baked in. No Funxy installation needed on the target:

funxy build healthcheck.lang -o healthcheck
scp healthcheck prod:~/
ssh prod './healthcheck'
Enter fullscreen mode Exit fullscreen mode

~25 MB binary, zero deps. Not tiny, but reasonable for a tool you're shipping to a server.

The script itself is straightforward:

import "lib/http" (httpGet)
import "lib/json" (jsonDecode)
import "lib/term" (green, red, table)

response = "https://api.example.com/services" |>> httpGet
services = response.body |>> jsonDecode

rows = []
for s in services {
    status = if s.healthy { green("●") } else { red("●") }
    rows = rows ++ [[s.name, status, s.version]]
}
table(["Service", "Status", "Version"], rows)
Enter fullscreen mode Exit fullscreen mode

You can embed static files (--embed templates, --embed "static/*.css"), and the script reads them via normal fileRead — doesn't matter if it's running interpreted or compiled. Cross-compilation works via --host flag (build on Mac for Linux).

Multi-command binaries

This is probably the feature I'm most happy with. You can bundle several scripts into one binary, BusyBox-style:

funxy build api.lang worker.lang migrate.lang -o myserver

./myserver api --port 8080    # runs api.lang
./myserver worker             # runs worker.lang
./myserver migrate            # runs migrate.lang
./myserver                    # prints usage
Enter fullscreen mode Exit fullscreen mode

Symlinks work too — ln -s myserver api && ./api --port 8080 dispatches by argv[0].

Embedded resources are shared across all commands, so --embed config is accessible from both api and worker. And sysArgs() strips the command name, so a script doesn't know or care if it's standalone or part of a multi-command binary.

One-liners

funxy -pe '1 + 2 * 3'                                                          # 7
echo '{"name":"Alice"}' | funxy -pe 'stdin |>> jsonDecode |> \x -> x.name'      # Alice
cat data.txt | funxy -lpe 'stringToUpper(stdin)'                                # per line
Enter fullscreen mode Exit fullscreen mode

-e eval, -p print, -l line mode. All stdlib auto-imported, piped input as stdin.

Built-in TUI

No external deps for colors, prompts, spinners, tables:

import "lib/term" (red, green, bold, confirm, select, table, spinnerStart, spinnerStop)

env = select("Deploy to", ["dev", "staging", "prod"])

if confirm("Deploy to " ++ bold(env) ++ "?") {
    s = spinnerStart("Deploying...")
    // ... work ...
    spinnerStop(s, green("✓ Done"))
}

table(["Service", "Status"], [["api", green("●")], ["cache", red("●")]])
Enter fullscreen mode Exit fullscreen mode

The language

Static types, full inference. Pattern matching with string patterns:

match (method, path) {
    ("GET", "/users/{id}")       -> getUser(id)
    ("GET", "/files/{...path}")  -> serveFile(path)
    _                            -> notFound()
}
Enter fullscreen mode Exit fullscreen mode

Multi-paradigm — imperative and functional in the same file:

// Imperative
total = 0
for item in order.items {
    total = total + item.price * item.qty
}

// Functional
total = order.items |> map(\i -> i.price * i.qty) |> foldl(\a, b -> a + b, 0)

// Side effects
users |> filter(\u -> u.active) |> forEach(\u -> print(u.name))
Enter fullscreen mode Exit fullscreen mode

ADTs, unions, records, closures, async/await, pipes, TCO.

Stdlib

HTTP/gRPC, JSON/CSV/Protobuf, SQLite (built-in), regex, crypto, WebSockets, async tasks — 31 modules. funxy -help lib/<name> for docs.

Install

curl -sSL https://raw.githubusercontent.com/funvibe/funxy/main/install.sh | bash
Enter fullscreen mode Exit fullscreen mode

macOS, Linux, FreeBSD. It's early — feedback, ideas and bug reports are very welcome.

GitHub logo funvibe / funxy

Funxy is a general-purpose scripting language with static typing and type inference

Funxy

A statically typed scripting language that compiles to native binaries. For automation, services, and data tooling.

  • Write scripts, ship native binaries — funxy build creates standalone executables with embedded resources
  • Static types with strong inference — most code needs no annotations
  • Batteries-included stdlib: HTTP/gRPC, JSON/protobuf, SQL, TUI, async/await, bytes/bits
  • Command-line eval mode (-pe, -lpe) for one-liners and shell pipelines
  • Safe data modeling with records, unions, ADTs, and pattern matching
  • Easy embedding in Go for config, rules, and automation
funxy build server.lang -o myserver && scp myserver user@prod:~/
Enter fullscreen mode Exit fullscreen mode
echo '{"name":"Alice"}' | funxy -pe 'stdin |>> jsonDecode |> \x -> x.name'   # Alice
Enter fullscreen mode Exit fullscreen mode
import "lib/csv"  (csvEncode)
import "lib/io"   (fileRead, fileWrite)
import "lib/json" (jsonDecode)
users = "users.json" |>> fileRead |>> jsonDecode
fileWrite("users.csv", csvEncode(users))
Enter fullscreen mode Exit fullscreen mode

Install

curl -sSL https://raw.githubusercontent.com/funvibe/funxy/main/install.sh | bash
Enter fullscreen mode Exit fullscreen mode




Top comments (0)