不可变数据(Immutability)
在 Elixir 中,所有数据都是不可变的。变量绑定的值一旦创建,就不能被修改——只能创建新值。这听起来像限制,实则是并发安全的基石。
# 变量绑定(不是赋值!)
x = 5
y = x + 1 # 创建新值 6,x 仍然是 5
# 列表"修改"返回新列表
list = [1, 2, 3]
new_list = [0 | list] # 前置元素,返回 [0, 1, 2, 3]
# list 仍然是 [1, 2, 3]
# 重绑定(允许,但不是原地修改)
x = 10 # x 现在绑定到新值 10,原来的 5 等待 GC
不可变性 vs 并发:因为数据不可变,多个进程可以安全地持有对同一数据结构的引用——它永远不会被其他进程修改。这消除了共享可变状态带来的竞态条件,无需锁。
基本数据类型
原子(Atom)
原子是以冒号 : 开头的常量,其值就是其名字本身。原子在内存中只存储一次,比较时是 O(1) 操作,大量用于标记、状态标志、键名。
:ok # 成功标志
:error # 错误标志
:pending # 自定义状态
true # true 本质是原子 :true
false # false 本质是原子 :false
nil # nil 本质是原子 :nil
# 常见使用模式:ok/error 元组
{:ok, result} = File.read("data.txt")
{:error, reason} = File.read("nonexistent.txt")
元组(Tuple)
元组用 {} 表示,是固定大小的有序集合,元素存储在连续内存中,按索引访问为 O(1)。常用于返回多值。
point = {10, 20}
{x, y} = point # 模式匹配解构
response = {:ok, 200, "OK"}
{status, code, message} = response
# elem/2 按索引访问
elem(response, 0) # :ok
elem(response, 1) # 200
# tuple_size/1
tuple_size(response) # 3
列表(List)
列表用 [] 表示,是链表结构(不是数组)。头部插入 O(1),按索引访问 O(n)。适合函数式编程的递归遍历模式。
nums = [1, 2, 3, 4, 5]
# 头尾操作
[head | tail] = nums
# head = 1, tail = [2, 3, 4, 5]
# 列表拼接
[1, 2] ++ [3, 4] # [1, 2, 3, 4]
# 列表差集
[1, 2, 3] -- [2] # [1, 3]
# 常用函数
List.first(nums) # 1
List.last(nums) # 5
length(nums) # 5
Enum.at(nums, 2) # 3(按索引)
映射(Map)
映射用 %{} 表示,键值对结构,键可以是任意类型。当键为原子时有便捷的点访问语法。
# 原子键映射(最常用)
user = %{name: "Alice", age: 30, admin: false}
# 访问
user.name # "Alice"(点语法,原子键专用)
user[:age] # 30
Map.get(user, :name) # "Alice"
Map.get(user, :phone, "N/A") # 带默认值
# 更新(返回新映射,原映射不变)
updated = %{user | age: 31}
# 添加/修改键
Map.put(user, :email, "alice@example.com")
# 字符串键映射
config = %{"host" => "localhost", "port" => 4000}
config["host"] # "localhost"
模式匹配:= 运算符
Elixir 中 = 不是"赋值",而是模式匹配运算符。它尝试让左侧的模式与右侧的值匹配,并绑定其中的变量。
# 简单绑定
x = 42
# 元组解构
{:ok, value} = {:ok, 123}
# value = 123
# 如果不匹配,抛出 MatchError
{:ok, _} = {:error, "not found"}
# ** (MatchError) no match of right hand side value
# _ 是通配符,忽略该位置
{_, second, _} = {1, 2, 3}
# second = 2
# 列表模式匹配
[first | rest] = [10, 20, 30]
# first = 10, rest = [20, 30]
# 嵌套匹配
%{user: %{name: name}} = %{user: %{name: "Bob", age: 25}}
# name = "Bob"
# Pin 运算符 ^ 固定变量值,不重绑定
x = 5
^x = 5 # OK,匹配成功
^x = 6 # ** (MatchError) 6 != 5
函数定义:def 与 defp
defmodule MathUtils do
# def — 公开函数
def add(a, b) do
a + b
end
# 单行简写
def square(x), do: x * x
# defp — 私有函数,模块外不可调用
defp validate(x) when x > 0, do: :valid
defp validate(_), do: :invalid
# 多子句函数(模式匹配分派)
def describe(0), do: "零"
def describe(n) when n > 0, do: "正数"
def describe(_), do: "负数"
end
管道运算符 |>
管道运算符 |> 将左侧表达式的结果作为第一个参数传给右侧函数,使数据处理链可以从上到下线性阅读,消除嵌套调用的"洋葱"代码。
# 没有管道:嵌套很难读
result = Enum.sum(Enum.filter(Enum.map([1,2,3,4,5], &(&1 * 2)), &(&1 > 4)))
# 使用管道:清晰表达数据流
result =
[1, 2, 3, 4, 5]
|> Enum.map(&(&1 * 2)) # [2, 4, 6, 8, 10]
|> Enum.filter(&(&1 > 4)) # [6, 8, 10]
|> Enum.sum() # 24
# 实际应用:处理用户输入
" hello world "
|> String.trim()
|> String.split()
|> Enum.map(&String.capitalize/1)
|> Enum.join(" ")
# "Hello World"
字符串插值
name = "世界"
greeting = "你好,#{name}!"
# "你好,世界!"
# 表达式插值
"1 + 1 = #{1 + 1}" # "1 + 1 = 2"
"大写:#{String.upcase("elixir")}" # "大写:ELIXIR"
# 多行字符串(heredoc)
text = """
第一行
第二行
"""
Guard 守卫子句
Guard(when 子句)允许在函数子句或模式匹配中添加额外条件,细化匹配逻辑。Guard 只能使用有限的纯函数(确保无副作用)。
defmodule Classifier do
def classify(n) when is_integer(n) and n > 0, do: "正整数"
def classify(n) when is_integer(n) and n < 0, do: "负整数"
def classify(0), do: "零"
def classify(f) when is_float(f), do: "浮点数"
def classify(s) when is_binary(s), do: "字符串"
def classify(_), do: "其他类型"
# Guard 中可用函数:is_integer/is_float/is_binary/is_list
# is_atom/is_map/is_nil/is_boolean/is_function
# abs/rem/div/length/map_size/byte_size/bit_size
# 比较运算符:>/>/==/!=/>=/=<
# 逻辑运算符:and/or/not(Guard 中不能用 &&/||)
end
实战:数据转换管道
综合运用本章知识,构建一个处理用户数据的转换管道:
defmodule DataPipeline do
@doc "处理原始用户数据列表,返回活跃管理员的邮箱列表"
def active_admin_emails(users) do
users
|> Enum.filter(&active?/1)
|> Enum.filter(&admin?/1)
|> Enum.map(&extract_email/1)
|> Enum.reject(&is_nil/1)
|> Enum.sort()
end
defp active?(%{status: :active}), do: true
defp active?(_), do: false
defp admin?(%{role: :admin}), do: true
defp admin?(_), do: false
defp extract_email(%{email: email}), do: email
defp extract_email(_), do: nil
end
# 测试数据
users = [
%{name: "Alice", role: :admin, status: :active, email: "alice@ex.com"},
%{name: "Bob", role: :user, status: :active, email: "bob@ex.com"},
%{name: "Carol", role: :admin, status: :inactive, email: "carol@ex.com"},
%{name: "Dave", role: :admin, status: :active, email: "dave@ex.com"}
]
DataPipeline.active_admin_emails(users)
# ["alice@ex.com", "dave@ex.com"]
本章小结:Elixir 的基础语法处处体现函数式思想:不可变数据保证并发安全;模式匹配 = 让数据解构自然流畅;管道运算符 |> 使数据流向一目了然;多子句函数 + Guard 替代繁冗的 if/else 分支。下一章深入函数式编程核心工具。