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

Install

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

Open Windows Terminal → Ubuntu/WSL tab, then run the same command.

Or download manually from releases, or build from source: git clone ... && cd funxy && make build (Go 1.23+).


Internal Architecture and Technical Design of Funxy

Funxy is a general-purpose scripting language with compile-time type checking, safe concurrency, and native integration with the Go ecosystem.

Below is an overview of the engine's subsystems and the implications of the architectural decisions made.

1. Execution Environment: Compilation Pipeline + Bytecode VM

In the main execution path, Funxy uses a lexer → parser → semantic analyzer → compiler pipeline, after which it executes the generated bytecode in a stack-based VM. The VM uses a compact stack of values (Value) and a call frame stack. The lightweight Value structure stores a scalar value (for unboxed Int, Float, Bool) and an interface pointer Object for reference types. This radically reduces the load on escape analysis and heap allocations during intensive computations.

Inside the VM, an evaluator instance is also brought up, acting as an auxiliary runtime layer for built-in mechanisms (builtin handlers, function calls/captures, async/RPC integration, and interop).

Distribution (Bundling): The compiled bytecode, along with arbitrary static resources, is serialized into a bundle and concatenated to the end of the runtime binary file (BusyBox pattern). At startup, the binary reads its bytecode, deserializes it, and immediately initializes the VM, bypassing the parsing and pre-processing stages.

2. State Management and Absence of GIL

In many popular scripting languages, there is a problem with the Global Interpreter Lock (GIL). Those embeddable engines that do not have a GIL (e.g., isolated Lua states) usually require manual synchronization when attempting to pass mutable state between threads. Funxy solves this problem through the concept of immutable data.

  • Persistent Data Structures: There are no mutable collections in the language.
    • Records: Structured objects (analogous to structs) are implemented as immutable sorted arrays of key-value pairs. Thanks to key sorting, field lookup occurs in O(log N) via binary search, and the records themselves occupy minimal memory footprint.
    • Maps and global VM namespaces are implemented as Hash Array Mapped Tries (HAMT).
    • Lists are implemented using a hybrid model: PersistentVector (providing O(1) access and appending to the end) in combination with a classic cons-representation.
  • Thread-Safety "Out of the Box": Any mutation operation (adding an element or key) does not change the original object but creates a new structure reusing existing nodes (structural sharing). This means that any internal data of the language can be safely passed between goroutines without mutexes.
  • Garbage Collection Optimization: Funxy relies on Go's concurrent garbage collector. The creation of many short-lived intermediate nodes is well-utilized by the garbage collector without causing long application pauses.
  • Transient Builders for Comprehensions: To maximize performance during bulk generation of persistent data structures (e.g. Map and List comprehensions), the VM utilizes a Transient Builder pattern. This accumulates data in a mutable structure localized entirely on the VM stack, converting it to an immutable HAMT/List only at the very end. This completely avoids allocating intermediate GC garbage and greatly speeds up operations.
  • Cheap Interpreter Cloning: Thanks to the immutability of the environment, forking in the VM happens very quickly, as new instances mostly receive pointers to the same immutable property trees. During fallback via evaluator, part of the global environment can be cloned linearly.

3. Concurrency: Asynchrony and Actor Model

Funxy provides two layers for working with concurrency: application (lib/task) and architectural (Virtual Machine Manager (VMM)).

Application Asynchrony (lib/task)

For everyday tasks (network requests, timers), the language provides the lib/task module — a wrapper over goroutines with Promises/Futures semantics.

  • Upon calling async(...), the virtual machine forks its context (ForkVM()). The asynchronous task executes in a new goroutine with its own stack, sharing immutable data with the parent thread.
  • Primitives await, awaitAll, awaitAny are implemented (with timeout support). Maximum parallelism is limited by the internal goroutine pool.

Actor Model: Go Hypervisor and VMM

For building microservice and fault-tolerant systems, a Supervisor/Worker architecture inspired by Erlang/OTP is used. The heart of the system is the Go Hypervisor — a lightweight host-level orchestrator that routes messages, manages the VM lifecycle, and controls resources. All virtual machines (actors) run within a single OS process.

  • Supervisor and State Handoff: The main script (Supervisor) manages the lifecycle of worker scripts (Workers). If a Worker crashes (panic), the Supervisor intercepts the event from the Hypervisor and can restart it. When creating a new Worker (spawnVM), the Supervisor can pass an initial state to it. To guarantee memory independence and long-term compatibility (if the state was read from a DB), this data is serialized into bytes (by default via the stable FDF format, configurable with the --rpc-serialization flag), passed across the Hypervisor boundary, and deserialized inside the new VM.
  • Isolation and Zero-Copy Communication: During operation, workers are isolated from each other and communicate exclusively via the RPC and Mailbox system. Since all data in the language is immutable, communication within a single process occurs without serialization and memory copying (Zero-Copy). Only a pointer to the root of the structure (HAMT-map, vector, or record) is passed to another VM.
  • Isolation Protection: To ensure that passing by reference does not lead to sharing mutable state and Data Races, the data undergoes serializability validation at the RPC/Mailbox boundary. Unsupported objects are rejected with an error.
  • Fault Tolerance: The Hypervisor implements the Circuit Breaker pattern. If a Worker hangs or starts throwing errors, its circuit opens (Open), and new RPC requests from other VMs are instantly rejected, preventing a cascading failure of the entire system.

Note: In the SupervisorHandler interface architecture, an RPC mechanism with full serialization is provided (Slow Path). In standard configuration, lib/rpc calls default to the fast-path (zero-copy). The slow-path with byte serialization remains as a compatible transport layer and an extension point for custom hosts/transports.

4. Type System and Lexical Semantics

Funxy provides the expressiveness of functional languages with the guarantees of static typing. At the same time, the language enforces strict lexical conventions that simplify parsing and make the code self-documenting.

  • Case-sensitive naming conventions: At the parser level, naming rules are strictly fixed:
    • All variables, functions, constants, and generic parameters must start with a lowercase letter (e.g., x, fetchData, <t>).
    • Only types, type constructors, and constructors of ADT variants are written with a capital letter (e.g., String, Result, Ok, Some).
  • Hindley-Milner Inference: The analyzer uses the Hindley-Milner algorithm, which allows omitting explicit type annotations in the vast majority of cases while maintaining strict typing and parametric polymorphism at compile time.
  • Algebraic Data Types (ADT): Full union types and exhaustive Pattern Matching are supported. The compiler forces the developer to handle all possible branching variants.
  • Type Erasure and Monomorphization: The language employs a hybrid approach to generics for maximum performance. At the level of data structures (List, Option), type erasure is applied — at runtime they exist as base types without parameters, saving memory. However, for generic functions, the bytecode compiler performs monomorphization: a separate optimized bytecode is generated at compile time for each specific combination of types. This significantly reduces the overhead of generic dispatch in the hot execution path.
  • MPTC and FunDeps: Function polymorphism (including operators) is implemented via typeclasses with support for multiple parameters and functional dependencies.

5. Integration with Host System (Native Go Interop)

Funxy is designed to seamlessly use the Go ecosystem. Calling a Go function from a script is a direct in-memory call, without the context switching inherent to FFI and CGO.

  • Automatic Code Generation: The developer declares dependencies in the config. During the funxy build build process, the utility scans the AST of Go packages (via go/packages) and generates native Go wrappers.
  • Flexible Signature Transformation (On Demand): Integration can be declaratively configured in the config:
    • Error Handling: With the error_to_result flag, the Go return pattern (T, error) is automatically converted into the safe Funxy algebraic type Result<String, T>.
    • Context Injection: When the skip_context flag is set, the code generator will automatically inject context for Go functions where context.Context is expected as the first parameter, hiding it from the script signature.
  • Support for Callback Functions: The code generator can handle passing functions as parameters (e.g., for methods like filepath.Walk). A Funxy closure is wrapped into a Go function, which under the hood calls ApplyFunction on the VM side.

Key Limitation: HostObject and Mutability

Integration with Go requires caution. Complex Go structures are passed into Funxy scripts as opaque pointers HostObject. Since Funxy cannot control the behavior inside Go, passing the same mutable Go pointer into multiple Workers or asynchronous tasks can violate Funxy's thread-safety guarantees. If scripts start concurrently calling methods of a Go object that mutate its state, a Data Race will occur unless this Go object is protected by its own mutexes. The responsibility for state safety inside the integrated Go code lies with the developer.

Conclusion

Funxy provides a combination rare for scripting languages: static type guarantees + functional data model + managed concurrency + deep integration with Go.

This makes the language particularly strong for embedded scenarios, high-concurrency environments, and infrastructure tooling where execution predictability, context isolation, and convenient access to production Go libraries are important.