XS: Một binary 2.9 MB chứa cả một toolchain lập trình
Cứ mỗi lần tôi học một ngôn ngữ mới, đều có một giai đoạn mà mình không viết code - mình cài thứ này thứ kia. Build system này, formatter kia, package manager thì đòi đúng một version nhất định của chính nó để boot. Lúc gõ được println("hello") thì cũng đã mất toi một tiếng đồng hồ vào các file config mà chẳng bao giờ mở lại.
XS không nghĩ vậy.
Một câu tóm tắt
XS là ngôn ngữ lập trình mà một binary 2.9 MB chứa compiler, language server, debugger, formatter, linter, test runner, profiler và package manager - và cùng một source code đó compile ra native machine code, JavaScript hay WebAssembly, chạy không cần sửa gì trên Linux, macOS, Windows, iOS, Android, ESP32 và Raspberry Pi.
Đây không phải roadmap. Đây là bản v1.2.24 đang ship.
curl -fsSL xslang.org/install | sh
xs --version
xs fmt # formatter
xs test # test runner
xs debug # debugger
xs upgrade # tự cập nhật
Không cần cài thêm gì. Không bị conflict version giữa formatter và type checker. Một binary.
--> // making it invisible to querySelectorAll. // // `data-cfasync="false"` keeps this rescue script executable even when // Rocket Loader is active. It rescues module scripts via two strategies: // 1. Query the DOM for type$="-module" + src (covers case A) // 2. Regex-parse the raw HTML for commented-out script tags (covers case B) // Dynamically-created scripts bypass Rocket Loader entirely. (function () { if (window.__markdyRescue) return; window.__markdyRescue = true; var rescued = false; function rescueModuleScripts() { if (rescued) return; rescued = true; var srcs = []; // Strategy 1: Rocket Loader kept the tag in DOM but changed the type. // type="module" → type="{uuid}-module" (still has src attribute) document.querySelectorAll('script[type$="-module"][src]').forEach(function (s) { srcs.push(s.src); }); // Strategy 2: Rocket Loader COMMENTED OUT the script tag entirely: // // These are invisible to querySelectorAll, so we parse the raw HTML. // We handle both attribute orderings (type-first or src-first). var html = document.documentElement.innerHTML; var reSrcFirst = //g; var reTypeFirst = //g; var m; while ((m = reSrcFirst.exec(html)) !== null) { srcs.push(m[1]); } while ((m = reTypeFirst.exec(html)) !== null) { srcs.push(m[1]); } // Re-inject each found src as a real module script. // Deduplicate first, then inject. Dynamically-created scripts bypass // Rocket Loader entirely. Modules with the same URL are only executed // once by the browser (cached), so re-injecting already-running scripts // is safe. var seen = {}; srcs.forEach(function (src) { if (seen[src]) return; seen[src] = true; var fix = document.createElement('script'); fix.type = 'module'; fix.src = src; fix.setAttribute('data-cfasync', 'false'); document.head.appendChild(fix); }); } // Rescue when user clicks the placeholder (fallback if autoplay failed). document.addEventListener('click', function (e) { var t = e.target; if (t && typeof t.closest === 'function' && t.closest('.markdy-placeholder')) { rescueModuleScripts(); } }); // Rescue automatically after a short delay for autoplay. // Only fires if initAll() never ran (no data-markdy-init on any root). setTimeout(function () { if (document.querySelector('.markdy-root:not([data-markdy-init])')) { rescueModuleScripts(); } }, 1500); }());Thiết kế có gì thú vị
Bản thân ngôn ngữ cố tình không phức tạp. Type là tùy chọn, và ý nghĩa là tùy chọn thật sự - type checker chỉ hoạt động trên code có annotation:
{- fib cơ bản, không cần type -}
fn fib(n) {
if n <= 1 { return n }
return fib(n - 1) + fib(n - 2)
}
println(fib(10))
Thêm annotation khi codebase phức tạp đến mức cần ràng buộc:
fn fib(n: int) -> int {
if n <= 1 { return n }
return fib(n - 1) + fib(n - 2)
}
Code chưa annotate thì qua không lọc. Ngoài ra còn decorator @memoize cho pure function, pattern matching với exhaustiveness checking, và algebraic effects - một tính năng khá hiếm, cho phép khai báo side effect như một named effect và để caller quyết định xử lý:
effect Ask {
fn prompt(msg) -> str
}
fn greet() {
let name = perform Ask.prompt("name?")
return "Hello, {name}!"
}
let result = handle greet() {
Ask.prompt(msg) => resume("World")
}
println(result)
Algebraic effects trong ngôn ngữ general-purpose là không phổ biến. Hầu hết ngôn ngữ hoặc tránh hẳn, hoặc ghép vào như một thư viện.
Một chi tiết cần biết trước: bytecode VM dùng global interpreter lock, chỉ nhả ra khi sleep, I/O hoặc channel receive. Giống hệt CPython. Hai thread tính toán thuần túy sẽ luân phiên thay vì chạy song song. Đây là một đánh đổi có chủ ý và ít nhất tác giả nói thẳng ra.
Sáu backend, một file source
Điểm đặc biệt nhất của XS là số lượng backend:
| Backend | Cách dùng | Ghi chú |
|---|---|---|
| Bytecode VM | xs (mặc định) | Chạy bình thường |
| JIT | xs --jit | Chỉ x86-64 và aarch64 |
| Tree-walk interpreter | xs --interp | REPL, debug AST |
| C transpiler | xs --emit c | C tự chứa, dùng với mọi compiler |
| JS transpiler | xs --emit js | Node hoặc browser, nhẹ hơn WASM |
| WASM | xs.wasm | Runtime trên browser |
Benchmark fib(30) trên Linux x86-64:
| JIT | VM | Interpreter |
|---|---|---|
| 31 ms | 62 ms | 138 ms |
Một điểm về chất lượng: interpreter và bytecode VM được diff với nhau ở mỗi commit. Nếu cho kết quả khác nhau thì build fail - dù từng backend riêng lẻ vẫn pass test. Với một project được làm bởi một người, đó là thanh chất lượng khá cao.
Toàn bộ project build từ 132 KLOC C source với gcc hoặc clang và GNU make. Không phụ thuộc bên ngoài nào khác. HTTPS được xử lý bởi BearSSL nhúng sẵn, không cần link vào OpenSSL của hệ thống.
HN nói gì
Bài đạt 11 điểm và năm comment - không lên được trang nhất. Nhưng hai comment có nội dung lại đi thẳng vào câu hỏi mà bất kỳ người đánh giá nghiêm túc nào cũng sẽ đặt ra.
Một người ghi nhận decorator @memoize rồi hỏi: điều đó có ngụ ý immutability không? Suy luận hợp lý - nếu một hàm có thể memoize an toàn, là vì input bất biến, hay ngôn ngữ đảm bảo referential transparency theo cách nào khác? Decorator thì có, nhưng docs không làm rõ đảm bảo gì.
Câu hỏi sắc hơn từ người thứ hai: “Nó có destructor không? Có memory-safe không?”
Docs im lặng hoàn toàn. Không có memory model, không có ownership system, không mô tả garbage collector - không trong phần giới thiệu, không trong reference. Với một ngôn ngữ nhắm tới ESP32 (microcontroller với 520 KB RAM, không có virtual memory), khoảng trống đó đáng kể. Hoặc câu trả lời nằm ở đâu đó trong reference mà tôi chưa tìm thấy, hoặc đây là câu hỏi mà phiên bản tiếp theo sẽ phải trả lời thẳng thắn. Source code C thì có sẵn để đọc, nhưng phải đọc source để hiểu memory behavior thì đó không phải dấu hiệu tốt cho tài liệu.
Có nên thử không?
| Nên thử nếu… | Có thể bỏ qua nếu… |
|---|---|
| Bạn muốn scripting language có sẵn toolchain, deploy trên mọi thứ có CPU | Bạn cần memory safety guarantee trước khi ship |
| Bạn thích đọc implementation ngôn ngữ nhỏ gọn, có quan điểm rõ ràng | Bạn cần hệ sinh thái package đã trưởng thành ngay bây giờ |
| Bạn làm embedded và muốn một tool làm hết mọi việc | Tổ chức bạn cần ngôn ngữ có track record 10 năm |
XS là một solo project nghiêm túc, biết mình muốn gì. Sự gọn gàng của toolchain là có thật. Thiết kế ngôn ngữ có những ý tưởng thực sự - algebraic effects, optional typing, sáu backend nhất quán từ một source. Liệu nó có đủ chiều sâu cho codebase lớn, và memory model có đúng đắn cho phần cứng nhúng hay không, là những câu hỏi mà 5 comment HN chưa trả lời được.
Playground chạy thẳng trên browser tại xslang.org/playground. Năm phút thử sẽ cho bạn biết nhiều hơn bài này.
Thảo luận trên Hacker News · Gửi bởi xs-lang
Hoang Yell
Một nhà phát triển phần mềm và là người kể chuyện kỹ thuật. Tôi đọc Hacker News mỗi ngày và kể lại những câu chuyện hay nhất ở đây — bằng tiếng Việt và tiếng Anh, cho người tò mò nhưng không có thời gian.