Axiom 0.7.8:用 Axiom 自身编写的函数式系统编程语言
Show HN: Axiom: The Functional Systems Langauge
Axiom 0.7.8 发布,这是一门开源函数式系统编程语言,编译器本身由超过 119,000 行 Axiom 代码写成,可从提交的 LLVM IR 经 llc 与 C 链接器重建并校验两代字节一致。它通过类型与效果系统在编译期捕获遗漏分支和 I/O 副作用,直接编译为含分配器与系统调用的原生二进制,无 VM 和 GC,已为 macOS 与 Linux arm64 提供预编译版本。
函数式系统语言
用强大的类型表达你的想法。在编译期捕获遗漏的分支并检查副作用。交付一个无需 VM 或垃圾回收器的原生二进制文件。
献给那些在意代码如何抵达机器的人。
01 / 设计即表达为你的想法建模的类型。02 / 该明确处必明确编译器检查的副作用。03 / 原生从始至终LLVM。直达机器。
看它实际运行
一个小程序。全貌尽显。
编写它。运行它。破坏它。
看编译器如何捕获变化之处。
shapes.ax — 从源码到可执行文件
你的下一个原生二进制文件从这里开始。
为 macOS 和 Linux arm64 预构建。安装及其他目标平台
curl -fsSL https://raw.githubusercontent.com/chrispaig3/axiom/trunk/scripts/install.sh | bash
早期阶段的语言。真正的编译器。看看今天已就绪的功能。
为什么选择 Axiom
小巧的语言。强大的能力。
函数式编程的表达力,加上通往原生代码的直接路径。每一个设计选择都给你实实在在可用的东西。
交付二进制文件。就这样。
你的程序编译为原生可执行文件,包含其分配器并直接调用内核。无需配置 VM。无需调优垃圾回收器。
scripts/check-freestanding.sh让副作用显式化。
将计算与 I/O 分离。编译器推断副作用,检查
restrict(no-io),并要求执行 I/O 的函数声明它。;@axiom:restrict(no-io)找到问题。继续前进。
获得精确的位置、稳定的错误代码,而且往往还有你的工具可以应用的修复方案。
axiom explain为 108 个错误代码中的每一个都设有页面。axiom explain AX3005为你而建。也为你的智能体而建。
统一的语法让代码生成可预测。结构化诊断、精确的范围和稳定的符号 ID,让你的工具获得与你审查其工作时所用相同的事实。
--diagnostic-format=ai阅读编译器。用 Axiom 编写。
编译器是超过 119,000 行的 Axiom 代码。一次干净的检出会用
llc和 C 链接器从提交的 LLVM IR 重新构建它,并且除非两代产物逐字节相同,否则会停止。scripts/bootstrap-from-seed.sh把 Rust 也带上。
让有用的 Rust 代码触手可及。在
extern块中声明函数;--crate会构建并链接该 crate。当 Rust 值的最后一个 Axiom 引用消失时,它会被丢弃。axiom build --crate DIR
导览
感受一下这门语言。
十个可运行的程序,从你的第一个数据类型到调用 Rust。探索代码,跟随注释,看看每个程序究竟打印出什么。
01/14 · parcels.ax
货运状态报告
带命名字段的和类型,穷尽匹配。Option 是内置的,所以缺失的值是一种类型,而不是 null。
- L6构造函数可以携带命名字段。
- L18
alert返回Option String,因为有些包裹无需处理。 - L31
optUnwrapOr在没有值的地方提供默认值。 - L33
{id:<9}填充到九列。格式字符串是编译的,而非在运行时解释。
parcels.ax
(import IO)
(import Err)
(data Parcel
(Ordered)
(InTransit { carrier : String, days : Int })
(Held { why : String })
(Delivered))
(:: status (-> Parcel String))
(fn (status p)
(match p
((Ordered) "packing")
((InTransit carrier days) (format "{carrier}, {days} days out"))
((Held why) (format "held at {why}"))
((Delivered) "delivered")))
(:: alert (-> Parcel (Option String)))
(fn (alert p)
(match p
((Ordered) (Some "not shipped yet"))
((InTransit _ _) None)
((Held why) (Some (format "call about {why}")))
((Delivered) None)))
(:: row (-> String Parcel Int))
;@axiom:effect(io)
(fn (row id p)
(let (
(s (status p))
(a (optUnwrapOr (alert p) "-"))
)
(println "{id:<9}{s:<22}{a}")))
(:: main Int)
;@axiom:effect(io)
(fn (main)
{
(println "parcel status action")
(row "AX-1041" Ordered)
(row "AX-1042" (InTransit "DHL" 2))
(row "AX-1043" (Held "customs"))
(row "AX-1044" Delivered)
0
})
axiom run parcels.ax
parcel status action AX-1041 packing not shipped yet AX-1042 DHL, 2 days out - AX-1043 held at customs call about customs AX-1044 delivered -
性能
小体积二进制。实测性能。
在这个 Collatz 基准测试中,Axiom 的运行时间与 C 和 Rust 相差仅几毫秒,二进制体积也接近 C。单一工作负载,单台机器,源码与方法均已提供,可供复现。
Axiom0.443 s
Rust0.448 s
C0.444 s
三者完成时间彼此相差均在五毫秒以内。Axiom 生成 LLVM IR,因此一个只含算术和分支的循环会得到与另外两者相同的机器码。
Axiom0.166 s
Rust0.117 s
C0.151 s
Axiom 是三者中最慢的,比 rustc 慢四十九毫秒,为 1.42 倍;比 clang 慢十五毫秒,为 1.10 倍。之所以公布,是因为这就是实测结果,由源码旁的脚本测得。
Axiom35,784 B
Rust469,608 B
C33,432 B
比 Rust 二进制小十三倍,比 C 大约百分之七,且内部完全没有 C 运行时。
Axiom0
Rust70
C1
整个程序都在文件里。加载时无需解析任何东西,因为已没有什么需要解析。
Apple M1,macOS 27.0.1,darwin-aarch64 · Axiom 0.7.8 · rustc 1.98.1 · clang 23.1.2 · 使用 hyperfine 1.20.0 计时。三者均打印 428343467。
如何测量,以及图表背后的表格
对 1..3,000,000 的 Collatz 步数求和并打印。有符号 64 位整数,无内存分配,热循环中无库调用。每个数字是其各次运行中的最佳值,而非平均值:干扰只会让运行变慢,因此最小值是最接近成本本身的估计。
各次运行是交错进行的,依次对每个二进制各重复一次,而这一修正改变了结果。第一轮将每个二进制分块运行,使 Axiom 落后 Rust 1.6 倍。那是一种假象:中途启动的后台构建会拖累它所落在的那个块。交替运行让每个二进制受到相同的干扰,于是三者彼此重合。
这三个程序都在仓库的 web/bench/ 中,旁边的 run-bench.sh 会生成此表的每一个单元格,因此可以重新运行,而不必仅凭信任接受。Go 和 Haskell 缺席,是因为机器上没有这两者的工具链,而本项目只公布它实测过的数字。一个微基准测试说明不了分配密集型工作的情况,scripts/bench-datastructures.sh 对此单独测量,结果没那么好看。
axiom build --input collatz.ax --output out-axiom
rustc -O collatz.rs -o out-rust
clang -O2 collatz.c -o out-c
| 测量项 | Axiom | Rust | C |
|---|---|---|---|
| 运行时间3,000,000 个 Collatz 序列 · hyperfine 取 20 次最佳,交错运行 | 0.443 s | 0.448 s | 0.444 s |
| 编译为原生二进制单文件,冷启动 · hyperfine 取 15 次最佳,交错运行 | 0.166 s | 0.117 s | 0.151 s |
| 二进制大小磁盘上的可执行文件 | 35,784 B | 469,608 B | 33,432 B |
| 未定义符号nm -u <binary> | wc -l | 0 | 70 | 1 |
对比
一组不同的取舍。
函数式类型、显式效应和原生输出集于一门语言。看看 Axiom 的选择与 Rust、Go 和 Haskell 相比如何。
AxiomRustGoHaskell
内存Bump 分配、引用计数,以及在你选择时机时使用区域。没有追踪式收集器,没有借用检查器。所有权与借用追踪式 GC追踪式 GC
二进制内部你的代码、它的分配器和原始系统调用。不调用任何 C 库函数。std 链接 C 库GC 和调度器运行时GHC 运行时系统
副作用按函数推断;用编译器检查的标签声明或限制。不追踪不追踪在类型中手动追踪
宏在类型检查前重写程序树;卫生宏。Token 流无Template Haskell
构建与运行axiom run f.ax:一步完成,无需构建文件。cargogo buildcabal 或 stack
开始使用
你的第一个原生二进制从这里开始。
Axiom 需要来自 LLVM 的 llc 和一个用于最终链接的 C 编译器。编译器本身用 Axiom 编写,因此无需先安装其他工具链。
安装 LLVM 和 C 编译器
xcode-select --installbrew install llvmexport PATH="$(brew --prefix llvm)/bin:$PATH"Command Line Tools 提供用于最终链接的 C 编译器;Homebrew 的 LLVM 提供
llc,它不在默认路径中。安装 Axiom
有预构建归档时使用预构建归档;否则从提交的种子构建。
curl -fsSL https://raw.githubusercontent.com/chrispaig3/axiom/trunk/scripts/install.sh | bashexport PATH="$HOME/.axiom/bin:$PATH"适用于 arm64 上的 macOS 和 Linux。它会验证归档的 SHA-256,然后在新编译器替换任何内容之前,构建并运行一个导入标准库的程序,并且它只会替换自己创建的安装。
创建项目并运行
axiom new写入一个程序和一个axiom.pkg清单。在项目内,run和build无需文件名。axiom new hello && cd hello && axiom runhello/Main.ax
(import IO) (:: main Int) ;@axiom:effect(io) (fn (main) { (println "Hello from Axiom! 🚀") 0 })axiom run
Hello from Axiom! 🚀
你最常用的命令
axiom check- 类型检查并验证每个效应声明,不生成代码
axiom build- 一个原生可执行文件,以项目命名
axiom test- 每个名称以 test 开头的函数
axiom fmt- 唯一规范布局
axiom explain AX3005- 任何诊断代码背后的完整解释
axiom symbols Main.ax --calls- AXSYM 中的声明、效应、稳定 ID 和调用边
axiom emit-llvm Main.ax -o Main.ll- 在汇编前检查 LLVM IR
axiom build Main.ax --obfuscate -o app- 可选的字面量掩码和内部符号混淆
axiom repl- 交互式会话,逐行编译为原生代码
设置你的编辑器 以获得高亮、跳转到定义、悬停和修复功能。
导入的模块来自入口目录、包依赖和显式搜索路径。设置 AXIOM_PATH 以添加额外模块根目录,设置 AXIOM_STDLIB 以覆盖已安装的库。模块搜索和编译器检查
运行平台
| 目标 | 预构建 | 说明 |
|---|---|---|
darwin-aarch64 | 是 | Apple 芯片。预构建归档。 |
linux-aarch64 | 是 | 预构建归档。 |
--target 可从任何主机为其中任何一个发出;只有最终链接需要该目标的链接器。仅源码目标:baremetal-aarch64、darwin-x86_64、freebsd-aarch64、freebsd-x86_64、linux-x86_64、windows-aarch64、windows-x86_64。--target 为它们发出,且 CI 不在那里运行测试组。此处“supported”的含义。
裸机 AArch64 使用由板卡定义的平台接缝和可选的 QEMU 冒烟运行。编译器不在 Windows 上运行:从 Linux 或 macOS 交叉编译到它。嵌入式目标
状态
了解哪些已就绪。看看接下来是什么。
Axiom 是 0.x:可供探索,仍在演进。从一个工具、一个实验或一次贡献开始。用这张状态图来决定它适合哪里;这些行反映项目经过测试的实现状态。
完成
- 函数与类型完成柯里化签名、刚性类型变量、精确返回类型。
- ADT / 数据类型完成带位置字段或命名字段的和类型。
- 模式匹配(
match)完成嵌套、穷尽,在每个函数中检查。 - 结构体完成逐字段
mut、泛型参数、自动渲染。 - Lambda / 函数值完成闭包、部分应用、
_洞。 - 循环完成对范围和容器使用
for,while。 - 系统调用完成在八个托管目标中的任何一个上,你的程序和内核之间都没有 libc。
- 模块可见性完成只有
pub会离开模块。 - 自举完成它所取代的 Rust 编译器已被删除。
可用,但有明确限制
- 效果强制;声明了两项限制两个推断缺口,均已声明:无法解析的调用被标记为不完整,且构造函数分配不计入。
- 宏部分模板无法生成
import或嵌套的macro,也无法测试两个绑定器是否相同。 - 并发语言形式,两种降级
parallel、通道、一个互斥锁和任务池;taskFold在仅标量的显式回调契约下回收每个已交付的答案。 - 区域语法受检查的作用域,以及带逃逸规则的注解签名标量可以离开区域;类型化提升已计划。
- 资源所有权可用文件、套接字、终端模式、通道和互斥锁在最后一个所有者放手时自行关闭或释放。
- FFI可用通过
extern块使用 Rust,密封所有者,以及为调用而借用的回调。 - 标准库可用集合、密码学、可选二进制混淆、日期、JSON、网络和一个嵌入式数据库。
- 错误处理可用;在系统调用接缝处采用
Result、try形式,以及跳过坏记录而不展开的处理程序。 - 编辑器支持可用语言服务器加一个 tree-sitter 语法。
已移除
- 类型类已替换由能力记录取代:接口是函数的结构体。
- 列表已移除序列是
(Vec T)。 - 元组已移除积是
struct,和是data。 - 线性类型已移除确定性回收是引用计数的职责。
尚未就绪:包索引与版本锁定、异步与调度器、能在 Windows 上运行的编译器(它可从其他平台构建 Windows 可执行文件),以及 arm64 之外的预构建归档。完整表格
常见问题
几件值得了解的事。
Axiom 可以用于生产环境吗?
对大多数团队来说还不能。它处于 0.x 阶段:核心语言(类型、匹配、结构体、循环、模块)已经完成,FFI、标准库和编辑器支持可用,宏是部分完成。状态表的每一行都指明了其背后的测试。
没有包索引,也没有绿色线程。这里有的是一个小型语言,其编译器你可以阅读,其声明都经过测试。
实现状态它有垃圾回收器吗?
没有追踪式回收器。内存来自基于 mmap 的 bump 分配器,每个堆块都带有引用计数,因此一个值在其最后一个引用消亡的瞬间就被释放,源码中无需写任何东西。文件、套接字、通道和互斥锁也是所有者:每一个在其最后一个引用消失时自行关闭或释放。当你希望在自选的时间点进行回收时,(region r body) 可一步回滚分配器。
内存模型“无 libc”究竟是什么意思?
Axiom 生成的代码及其标准库通过原始系统调用访问内核:包括打印、分配、文件、进程和套接字。如果生成的 IR 调用了 C 库函数,或可执行文件导入了 C 库函数,scripts/check-freestanding.sh 会使构建失败。
最终链接由你系统的 C 编译器完成,在 Linux 上它会加入 C 运行时的启动代码;在 macOS arm64 上,对编译后程序运行 nm -u 的结果为空。通向 Rust 的 extern 块是唯一的出口,而 no_std crate 不会通过它添加任何 C 库函数。
独立环境门禁为什么用 S 表达式?
因为程序本身已经是一棵树。没有需要记忆的运算符优先级,没有歧义的解析,宏操作的结构与编译器检查的结构相同。这种一致性也正是该语言易于被工具或智能体正确生成的原因。
编辑器方面也已覆盖:tree-sitter 语法按语法角色着色并配有彩虹括号,axiom fmt 负责布局。
我能让分发的二进制文件更难被检查吗?
选择 axiom build Main.ax --obfuscate -o app 可掩蔽字符串字面量、打乱内部函数名并剥离局部符号。全新的构建种子使每次构建都不同。该模式在优化过程中保留不透明函数条目,并省略源码级回溯表。
Crypto.Obfuscate 还会打包带有上下文绑定、拆分密钥份额和显式密钥擦除的认证加密资产。这些特性使静态检查更加困难;但检查运行中的进程的人仍可恢复嵌入的密钥和解码后的数据。静态归档不接受该编译器标志。
二进制与资产混淆工具如何检查我的程序?
axiom --diagnostic-format=ai symbols Main.ax --calls 会发出 AXSYM 声明、稳定 ID、作者标签、派生效果和调用边。Agent.Tags 读取该流,包括效果和宏声明。符号请使用 AI 格式;JSON 格式会被拒绝。
symbols --mir --axir 会公开分析与检查记录。原生构建会把经过检查、展开的语法树编译为 LLVM IR。使用 emit-llvm 查看该输出。
跟随一个程序走完编译器我必须为每个函数的效应都加注解吗?
不必。有四种效应必须通过调用链声明:effect(io)、effect(entropy)、effect(spawn) 和 effect(block)。分配与修改会被推断并报告。执行原始操作、调用 extern 或将值转换为无关引用类型的函数,需声明 effect(unsafe)。受信任的包装器会为其调用者承担该义务。restrict(...) 和 pure 会在你需要的地方添加更强的检查。
参考中的效应有 trait 或类型类吗?
它们已被能力记录取代。接口是一个持有函数的参数化结构体,而实例就是它的一个普通值,在需要的地方传递。分派即应用:没有表,没有解析规则,并且对接口泛型的函数可以调用其方法。
能力记录有包管理器吗?
一个项目就是一个 axiom.pkg 文件:axiom new 会写入一个。depend 行指定一个模块目录或一个 git URL,axiom fetch 会克隆这些 URL,并且两个依赖项永远不能提供同一个模块。目前没有中央索引,也没有版本锁定,编译器绝不会自行获取或运行另一个项目的构建。
参考中的包为什么编译器有一种“ai”输出格式?
供智能体和其他工具使用。--diagnostic-format=ai 会为每条诊断打印一行,包含精确的跨度以及任何适用的修复,而 axiom symbols 会为每个声明打印一行,带有一个在重新格式化后仍保留的 id,这样工具无需再次读取文件就能了解它提供了什么。人类可读的报告由同一诊断渲染而来,因此两者不会不一致。
从想法到可执行文件
用类型思考。
构建原生程序。
你的第一个程序只需几条命令。试用这门语言,检查编译器,并帮助塑造接下来的发展。
来源:Hacker News · axiomlang.software