Elixir vs Go / Clojure / C / Java / Kotlin
· · ·
一节课把 Elixir 跟 5 个主流语言的核心差异过一遍,速查表为主。
题目进度 0 / 0 ✓ 0
**预计时间:**30 分钟。
本课前提:你已经读过 0001-0003,对 Elixir 语法、并发、协议/行为/宏都有印象。这课是横向对照——你已知的语言在每个点上跟 Elixir 怎么对应。
一、总览:Elixir 在语言谱系里位置
| 维度 | Elixir | 最像 | 最不像 |
|---|---|---|---|
| 范式 | 函数式 + Actor | Clojure / Haskell | C / Java |
| 运行平台 | BEAM (Erlang VM) | JVM (Clojure) | Native (Go/Rust/C) |
| 类型 | 动态(可选 spec) | Clojure / JS | Haskell / Rust / Java |
| 并发模型 | 消息传递 (Actor) | Akka / Erlang | C thread / Java Thread |
| 元编程 | 宏(homoiconic) | Clojure / Racket | Go / Java |
| 生态成熟度 | 小但精(Web/Phoenix) | Clojure(小) | Java(巨大) |
二、Elixir vs Go
两者都强调并发,但哲学不同:
| 维度 | Elixir / BEAM | Go |
|---|---|---|
| 并发单位 | 进程(独立堆) | goroutine(共享堆) |
| 通信 | 消息 send(pid, msg) | channel ch <- msg |
| 共享内存 | 不允许(消息传递唯一) | 允许(但要 mutex 同步) |
| 崩溃处理 | Supervisor 重启(“let it crash”) | panic / recover,goroutine 死了就死 |
| 调度 | 抢占式(reductions 计数) | 协作式(M:N) |
| 类型 | 动态 | 静态(Go 1.18+ 有泛型) |
| 部署 | mix release → 整目录(含 VM) | 单二进制(静态链接) |
| 热升级 | 原生支持 | 不支持 |
| 元编程 | 宏(强) | 无(go generate 凑合) |
| 生态焦点 | Web / 消息 / 电信 | 云原生 / CLI / 基础设施 |
代码对照:
# Elixir:异步计算
task = Task.async(fn -> heavy_calc(42) end)
result = Task.await(task) # 阻塞等
# Go:异步计算
ch := make(chan int)
go func() { ch <- heavyCalc(42) }()
result := <-ch # 阻塞等
# Elixir:消息循环
def loop do
receive do
:stop -> :ok
msg -> handle(msg); loop()
end
end
# Go:goroutine + select
for {
select {
case msg := <-ch: handle(msg)
case <-stopCh: return
}
}
何时选谁:
- **Elixir 强:**长连接、聊天、IM、WebSocket、电信、Phoenix LiveView
- **Go 强:**CLI 工具、K8s operator、单二进制部署、强类型
三、Elixir vs Clojure
最像的兄弟——同源于 Lisp 家族,都强调不可变 + 宏 + FP。
| 维度 | Elixir / BEAM | Clojure / JVM |
|---|---|---|
| 运行平台 | BEAM(软实时,30 年验证) | JVM(生态巨大,GC pause) |
| 不可变数据结构 | 基础:List/Tuple/Map(可变性看 GC) | 内置:Persistent Vector / HashMap / Tree |
| 并发模型 | Actor / 进程消息 | core.async channel / 原子 / ref |
| 宏 | 支持(需卫生手动) | 支持(完全卫生) |
| 类型 | 动态 | 动态(可选 spec) |
| 启动时间 | ~ 1 秒(VM 启 + 监督树) | ~ 3-10 秒(JVM 启) |
| 宏卫生性 | 手动(var!) | 完全自动(syntax-quote) |
| 生态库 | 小(Hex 30 万+ 包) | 巨大(任何 Java 库都能调) |
| 状态共享 | 消息 / ETS | atom / ref / agent |
| 主要应用 | Web (Phoenix) / 消息 / 嵌入式 | 数据分析 / 异步 / 后端服务 |
代码对照:
# Elixir:管道
list
|> Enum.filter(&(&1 > 0))
|> Enum.map(&(&1 * 2))
|> Enum.sum()
# Clojure:threading macro
(->> list
(filter pos?)
(map #(* % 2))
(reduce +))
# Elixir:defstruct
defmodule User do
defstruct [:name, :age]
end
# Clojure:defrecord
(defrecord User [name age])
# Elixir:case
case result do
{:ok, v} -> ...
{:error, e} -> ...
end
# Clojure:case / match
(case result
[:ok v] ...
[:error e] ...)
何时选谁:
- **Elixir 强:**需要 99.999% 可用性、长连接、软实时(电信/IM)
- **Clojure 强:**需要 JVM 生态、数据分析、Spark/Storm 集成
四、Elixir vs C
完全不同的物种——C 是底层,Elixir 是高级抽象。
| 维度 | Elixir | C |
|---|---|---|
| 运行平台 | BEAM 字节码 | Native(编译到机器码) |
| 内存模型 | 不可变,进程独立堆 | 裸指针,手动管理 |
| 类型 | 动态(unlimited precision int) | 静态(int 32/64) |
| 并发 | 进程 / 消息 / 抢占调度 | pthread / mutex(要自己写) |
| 性能 | 比 C 慢 5-10x(GC 开销) | 最快 |
| 安全性 | 无内存漏洞(无指针) | buffer overflow 等 |
| 互操作 | 通过 NIF 调 C | 调所有系统调用 |
代码对照(同样”求平方和”):
// C
int sum_of_squares(int arr[], int n) {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += arr[i] * arr[i];
}
return sum;
}
# Elixir
def sum_of_squares(arr) do
arr
|> Enum.map(&(&1 * &1))
|> Enum.sum()
end
何时选谁:
- **Elixir 强:**业务逻辑、分布式系统、不可变数据流
- **C 强:**操作系统、嵌入式、性能关键的库(BEAM 内部用 C 实现)
五、Elixir vs Java
JVM vs BEAM——都强调”平台 + 生态”,但运行时哲学不同。
| 维度 | Elixir / BEAM | Java / JVM |
|---|---|---|
| 并发单位 | BEAM 进程(~ 0.5 KB) | Thread(~ 512 KB) |
| 共享内存 | 不允许 | 堆共享,靠 synchronized |
| 崩溃 | let it crash + supervisor | try-catch + 重试 |
| GC | 每进程独立(无 global pause) | 全堆 GC(G1/ZGC 有停顿) |
| 类型 | 动态(spec 可选) | 静态(编译期检查) |
| OOP | 无(用进程 + 消息) | 强制(class / interface) |
| 部署 | mix release | JAR + JVM |
| 生态规模 | 小 | 巨大 |
| 主要应用 | Web (Phoenix) / 消息 / 电信 | 企业应用 / Android / 大数据 |
代码对照:
// Java
class UserService {
public User getUser(Long id) throws UserNotFoundException {
Optional<User> u = userRepo.findById(id);
return u.orElseThrow(() -> new UserNotFoundException(id));
}
}
// Elixir
defmodule UserService do
def get_user(id) do
case Repo.get(User, id) do
nil -> {:error, :not_found}
user -> {:ok, user}
end
end
end
关键直觉差异
Java 用
Java 哲学:"异常是异常情况"。Elixir 哲学:"errors are values, not exceptions"。
throw + try-catch 处理"预期错误",Elixir 用 {:ok, x} / {:error, reason}。Java 哲学:"异常是异常情况"。Elixir 哲学:"errors are values, not exceptions"。
六、Elixir vs Kotlin
Kotlin 是 JVM 上的”现代 Java”,加了协程。Elixir 在 BEAM 上原生做这些。
| 维度 | Elixir | Kotlin / JVM |
|---|---|---|
| 范式 | 纯函数式 | 多范式(OOP + FP + 协程) |
| 并发 | Process / 消息 | Coroutine / suspend |
| 类型 | 动态 | 静态(强类型,可空) |
| 不可变 | 默认 | 需要 val + data class + copy |
| 空安全 | 无(nil 自由) | 有(?. 强制处理) |
| 平台 | BEAM | JVM / Native / JS / Wasm |
代码对照(Kotlin Coroutine vs Elixir Process):
// Kotlin
suspend fun fetchUser(id: Long): Result<User> = coroutineScope {
val deferred = async { api.getUser(id) }
val profile = async { api.getProfile(id) }
Result.success(User(deferred.await(), profile.await()))
}
// Elixir
def fetch_user(id) do
Task.async_stream([:api_get_user, :api_get_profile], fn endpoint ->
apply(Pipeline.Api, endpoint, [id])
end)
|> Enum.map(fn {:ok, x} -> x end)
|> case do
[user, profile] -> {:ok, %{user: user, profile: profile}}
_ -> {:error, :incomplete}
end
end
七、6 个语言速记口诀
用一句话记住每个语言最适合什么:
| 语言 | 一句话定位 |
|---|---|
| Elixir | 消息 / 长连接 / 软实时,30 年电信验证 |
| Go | 云原生 / 基础设施 / 单二进制部署 |
| Clojure | JVM 上的 FP / 数据分析 / 不可变优先 |
| C | 操作系统 / 嵌入式 / 性能基准线 |
| Java | 企业应用 / Android / JVM 生态 |
| Kotlin | 现代 JVM / 空安全 / Coroutine |
八、Elixir 选型决策树
需要同时支持多少用户?
├── < 1000 并发 → 任何语言都行,选你熟悉的
├── 1000-10000 → Go / Kotlin 够用
├── 10000-100000 → Go / Elixir 都能上
├── 100000+ → Elixir / Erlang 强项
└── 1000000+ → 几乎只能 BEAM / Rust + 异步 I/O
需要多少 9?
├── 99% → 任何语言
├── 99.9% → Go / Java / Kotlin
├── 99.99% → Java / Elixir
└── 99.999%(5 个 9)→ Elixir / Erlang
需要 软实时(几十 ms 内响应)?
├── 是 → Elixir / Erlang / C
└── 否 → 任何语言
需要 WebSocket / 长连接很多?
├── 是 → Elixir / Phoenix LiveView 强项
└── 否 → 任何语言
九、回到本项目:ex/elixir 为什么选 Elixir?
这个项目(n8n 风格的工作流管道)选 Elixir 的原因:
- 轻量级进程:每个 DAG 步骤可以是独立进程,互不阻塞
- 监督树:外部 API(飞书 / 企微)挂了,supervisor 重启不影响其他
- Oban:基于 PG 的持久化任务队列——任务持久化在 DB,重启不丢
- 热代码:开发时改一行代码秒级生效(无 Spring Boot 那种几十秒启动)
- 错误处理:
{:ok, x}/{:error, e}让 worker 失败重试逻辑极清晰 - Ecto:PG 集成 + 动态查询 + changeset 校验,跟 Spring Data JPA 思路相似
十、测验(8 道对比)
1
Elixir 处理"预期错误"的主要方式?
2
Go 的并发模型和 Elixir 最大的差异?
3
Clojure 跟 Elixir 最像的一点?
4
Java 的 Thread 和 Elixir 进程的区别?
5
Kotlin Coroutine 跟 Elixir 进程的对比?
6
哪个语言最像 Elixir 的运行平台?
7
99.999% 可用性的应用选哪个?
8
Phoenix LiveView 主要擅长?
**下一步:**看 Lesson 0005 · 综合测验 + 代码练习。