Funxy (фанкси, fun x(y)) — гибридный язык программирования со статической типизацией, pattern matching и удобной работой с бинарными данными.
Гибридный означает сочетание императивного и функционального стилей. Можно писать привычные конструкции if/for, а можно — map/filter/match с pipes и композицией. Зависит от задачи и ваших предпочтений — стили спокойно можно смешивать.
Статическая типизация с выводом типов — компилятор проверяет типы до выполнения, но в большинстве случаев их не нужно указывать явно:
import "lib/list" (map) // Типы выводятся автоматически numbers = [1, 2, 3] doubled = map(fun(x) { x * 2 }, numbers) // Можно указать явно, если нужно fun add(a: Int, b: Int) -> Int { a + b }
Для чего подходит
Скрипты и автоматизация. Один бинарник без зависимостей — скачал и работает. Встроенная работа с файлами, JSON, HTTP, SQL.
Небольшие приложения. CLI-утилиты, API-сервисы, обработка данных.
Работа с бинарными данными. Парсинг на уровне отдельных битов. Сетевые протоколы, форматы файлов, нестандартные структуры.
Обучение программированию. Простой синтаксис, но с важными концепциями: типы, pattern matching, иммутабельные структуры данных, рекурсия с TCO (можно писать рекурсивный код без страха переполнения стека).
Для кого
Для тех, кто интересуется новыми языками, пишет утилиты и прототипы, хочет попробовать функциональный стиль. Язык экспериментальный — фидбек приветствуется.
Возможности
Pattern matching
fun describe(n) { match n { 0 -> "zero" n if n < 0 -> "negative" _ -> "positive" } } // Деструктуризация в match user = { name: "admin", role: "superuser", age: 25 } result = match user { { name: "admin", role: role } -> "Admin: ${role}" { name: name, age: age } if age >= 18 -> "Adult: ${name}" _ -> "Guest" } print(result) // Admin: superuser
String patterns
// Пример роутинга fun route(method, path) { match (method, path) { ("GET", "/users/{id}") -> "User ID: ${id}" ("GET", "/files/{file...}") -> "File: ${file}" _ -> "Not found" } } print(route("GET", "/users/42")) // User ID: 42 print(route("GET", "/files/css/main.css")) // File: css/main.css print(route("POST", "/other")) // Not found
{id}— захватывает сегмент пути до следующего/. Переменнаяidполучает типString.{file...}— greedy-захват, забирает весь остаток пути. Например, для/files/css/main.cssпеременнаяfileбудет"css/main.css".
Роутинг прямо в pattern matching, без отдельной библиотеки.
Pipes
import "lib/list" (filter, map, foldl) result = [1, 2, 3, 4, 5] |> filter(fun(x) { x % 2 == 0 }) |> map(fun(x) { x * x }) |> foldl(fun(acc, x) { acc + x }, 0) // 20
Работа с битами
import "lib/bits" (bitsExtract, bitsInt) // TCP-флаги: 6 бит 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) }
Алгебраические типы данных
type Shape = Circle Float | Rectangle Float Float fun area(s: Shape) -> Float { match s { Circle r -> 3.14 * r * r Rectangle w h -> w * h } } // Option и Result встроены fun safeDivide(a, b) { if b == 0 { Zero } else { Some(a / b) } }
В декларации типа скобки опциональны: Circle Float или Circle(Float). При создании значения используются скобки: Circle(2.0), Rectangle(3.0, 4.0).
Tail Call Optimization
fun countdown(n, acc) { if n == 0 { acc } else { countdown(n - 1, acc + 1) } } print(countdown(1000000, 0)) // работает, стек не переполняется
TCO работает и для взаимной рекурсии — когда функции вызывают друг друга. Это важно для state-машин:
fun isEven(n) { if n == 0 { true } else { isOdd(n - 1) } } fun isOdd(n) { if n == 0 { false } else { isEven(n - 1) } } print(isEven(1000000)) // true, без переполнения стека
Циклические зависимости
Модули могут импортировать друг друга — анализатор корректно разрешает циклы.
Один бинарник
./funxy script.lang # запуск ./funxy -help lib/http # документация ./funxy playground/playground.lang # веб-playground
Скачал или собрал сам — работает.
Пример: JSON API
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" } } } // Тест роутинга print(handler("GET", "/api/users")) print(handler("GET", "/api/users/42")) print(handler("DELETE", "/api/users/1"))
Стандартная библиотека
lib/http — HTTP-клиент и сервер. В сочетании со string patterns получается компактный роутинг без внешних зависимостей.
lib/json — кодирование и декодирование JSON. Records, списки, примитивы преобразуются автоматически.
lib/bits + lib/bytes — парсинг бинарных данных на уровне отдельных битов. Под капотом funbit — Go-реализация Erlang bit syntax. Удобно для сетевых протоколов и форматов файлов.
lib/sql — встроенный SQLite, работает сразу, без установки драйверов.
lib/task — асинхронные вычисления. async создаёт задачу, await ждёт результат, awaitAll — параллельное выполнение.
Полный список:
Модуль | Что делает |
|---|---|
lib/list | map, filter, foldl, sort, zip, range |
lib/string | split, trim, replace, contains |
lib/map | работа с ассоциативными массивами |
lib/json | jsonEncode, jsonDecode |
lib/http | httpGet, httpPost, httpServe |
lib/ws | WebSocket клиент и сервер |
lib/sql | SQLite |
lib/bits | парсинг на уровне битов |
lib/bytes | работа с байтами |
lib/task | async/await |
lib/crypto | sha256, md5, base64, hmac |
lib/regex | регулярные выражения |
lib/io | файлы и директории |
lib/sys | аргументы, переменные окружения, exec |
lib/date | дата и время |
lib/uuid | генерация UUID |
lib/math | математические функции |
lib/bignum | BigInt, Rational |
lib/test | unit-тесты |
lib/log | структурированное логирование |
Как попробовать за 1 минуту
# Скачать релиз или собрать из исходников git clone https://github.com/funvibe/funxy cd funxy make build # Hello World echo 'print("Hello, Funxy!")' > hello.lang ./funxy hello.lang # или просто echo 'print("Hello, Funxy!")' | ./funxy # Или запустить playground ./funxy playground/playground.lang # Открыть http://localhost:8080
Что в комплекте
Бинарник
funxy(macOS, Linux, Windows, FreeBSD, OpenBSD)Исходный код на Go
Документация: tutorial + справочник
Playground — веб-интерфейс для запуска кода
Поддержка синтаксиса для VS Code и Sublime Text
Производительность
Funxy — tree-walking интерпретатор. Это значит:
Где быстро:
TCO (хвостовая рекурсия) — миллион вызовов за 700ms
I/O операции — HTTP, файлы, SQL, JSON — bottleneck в I/O, не в языке
List операции — map/filter/foldl оптимизированы
Где медленно:
Интенсивные вычисления без TCO
Классический бенчмарк — числа Фибоначчи с наивной рекурсией O(2^n):
import "lib/time" (clockMs) fun fib(n) { if n < 2 { n } else { fib(n - 1) + fib(n - 2) } } start = clockMs() result = fib(35) print("fib(35) = ${result}") print("Time: ${clockMs() - start} ms")
Результат: ~17 секунд. Python делает то же за ~0.7 секунды (bytecode VM).
Это намеренно неэффективная реализация — стандартный бенчмарк, который показывает overhead интерпретатора на вызовах функций. С TCO-версией (аккумулятор) результат мгновенный:
fun fibFast(n, a, b) { if n == 0 { a } else { fibFast(n - 1, b, a + b) } } print(fibFast(35, 0, 1)) // 9227465, ~0ms
Для скриптов и утилит это не проблема — основное время уходит на I/O. Для числодробилок Funxy сейчас не подходит.
Ближайшие планы
Переключаемые бэкенды. Сейчас один интерпретатор (tree-walk). Планируется архитектура с несколькими бэкендами:
./funxy script.lang # default (tree-walk) ./funxy --backend=vm script.lang # stack-based VM
Stack-based VM — ожидается ускорение в 10-20 раз на вычислениях за счёт:
Линейного массива инструкций вместо обхода AST
Доступа к переменным по индексу вместо хеш-таблицы
Компактного представления (bytecode)
Tree-walk останется как reference implementation и для отладки.
Ссылки
Funxy на GitHub: github.com/funvibe/funxy
