- 文档
- 教程
【免费下载链接】learnxinyminutes-docs
Code documentation written as code! How novel and totally my idea!
Elixir 是一门构建在 Erlang VM(BEAM)之上的现代函数式编程语言,本仓库 ro/elixir.md 以"代码即文档"的形式,用罗马尼亚语完整演示了 Elixir 的核心语法与并发模型。本文以该文档为骨架,逐节拆解其涵盖的基本类型、操作符、控制流、模块与函数、结构体与异常、进程并发及 Agent 状态管理,并结合仓库中英文原版 elixir.md、zh-cn/elixir.md 及 CONTRIBUTING.md 的风格约定,帮助读者快速掌握可直接复制运行的 Elixir 代码片段,理解这门语言与 Erlang 生态的深层关系。
文档定位:Learn X in Y Minutes 项目的"可运行教程"范式
本仓库是 Learn X in Y Minutes 项目的文档库,README.md 将其宗旨概括为"以有效、带注释的代码形式呈现热门编程语言的旋风式导览"——即用一段可执行的代码,而非长篇论述来讲解一门语言。这一范式在 Elixir 教程中体现得淋漓尽致:
- 全文主体是一个完整的
elixir代码块,每一行都是经过验证的合法 Elixir 表达式,注释即讲解; - 代码块顶部直接给出环境前提:用
iex命令进入 Elixir 交互式 Shell,用elixirc命令编译模块,二者需正确安装 Elixir 后位于 PATH 中; - CONTRIBUTING.md 的风格指南要求"代码示例优先于文字说明""尽量少用文字",这正是该文档"注释即教程"的写作依据;
- 仓库 lint/frontmatter.py 会对每篇文档的 YAML 头(contributors、translators、filename 等字段)做格式校验,lint/encoding.sh 则确保所有 Markdown 文件保持 UTF-8 无 BOM 编码——这些质量保障机制保证了教程代码的可信度。
本教程由四位贡献者撰写(英文版),并由 Vitalie Lazu 翻译为罗马尼亚语,与英文原版 elixir.md 内容一一对应,是理解 Elixir 的完整入门素材。
基础类型:从原子到位串的数据全景
教程首先用一组最小表达式勾勒出 Elixir 的全部基础类型,每种类型都伴随可立即在iex中验证的示例。
数字、原子与元组
3 # 整型(integer) 0x1F # 十六进制整型,值为 31 3.0 # 浮点型(float) :salut # 原子(atom):以冒号开头的常量,其值就是自身名字 {1,2,3} # 元组(tuple):在内存中连续存储 elem({1, 2, 3}, 0) #=> 1(用 elem/2 按下标取元素,下标从 0 开始)原子是 Elixir 中极具辨识度的类型——它既是布尔值true/false的底层实现,也是消息协议、函数标签的常用载体,这在后文case模式匹配与并发消息示例中会反复出现。元组因内存连续存放,适合固定数量、少量元素的场景(如函数返回{:ok, result}或{:error, reason}二元组)。
列表、模式匹配与二进制
[1,2,3] # 列表(list):底层是链表实现 # 每个非空列表都有头(head,首元素)和尾(tail,其余元素组成的列表) [cap | coadă] = [1,2,3] cap #=> 1 coadă #=> [2, 3] <<1,2,3>> # 二进制(binary)教程在此处点出 Elixir 最重要的心智模型:=表示模式匹配(pattern matching)而非赋值。左侧是"模式"(pattern),右侧是"值",表达式两侧必须结构一致才能匹配成功。列表的[head | tail]解构正是这一语义的直接应用。若两侧不匹配则抛出MatchError:
{a, b, c} = {1, 2} #=> ** (MatchError) no match of right hand side value: {1,2}字符串、字符列表与 UTF-8 编码
"salut" # Elixir 字符串(string) 'salut' # Erlang 字符列表(char list) """ Sunt un șir de caractere pe mai multe linii. """ # 多行字符串(heredoc) "Bună dimineața" #=> "Bună dimineața"(字符串一律 UTF-8 编码) <<?a, ?b, ?c>> #=> "abc"(字符串本质是二进制) [?a, ?b, ?c] #=> 'abc'(字符列表本质是列表) ?a #=> 97(`?a` 返回字符 a 的 ASCII 码)这段代码揭示了 Elixir 的经典"坑与美":双引号字符串实际上是 UTF-8 编码的二进制(bitstring),单引号字符列表则是普通列表。教程用?a返回 97 来说明两者的转换关系。相应地,拼接操作符也因此分道扬镳——链表用++,二进制用<>:
[1,2,3] ++ [4,5] #=> [1,2,3,4,5] 'Salut ' ++ 'lume' #=> 'Salut lume' <<1,2,3>> <> <<4,5>> #=> <<1,2,3,4,5>> "Salut " <> "lume" #=> "Salut lume"区间与映射
1..10 # 区间(range):两端均含端点 început..sfîrșit = 1..10 # 区间同样支持模式匹配 [început, sfîrșit] #=> [1, 10] # 映射(map):键值对容器 genuri = %{"Ion" => "bărbat", "Maria" => "femeie"} genuri["Ion"] #=> "bărbat" # 键为原子的映射有专用语法与点访问 genuri = %{ion: "bărbat", maria: "femeie"} genuri.ion #=> "bărbat"映射是 Elixir 中用途最广的复合数据结构,教程同时展示了=>通用键和key:原子键两种写法,后者可直接用点号访问字段——这正是后文struct结构的语法基础。
操作符:严格布尔、宽松逻辑与全序比较
教程的操作符章节刻意区分了两套逻辑操作符,这是新手最容易混淆的地方:
1 + 1 #=> 2 10 / 2 #=> 5.0(除法永远返回浮点数) div(10, 2) #=> 5(整数除法) rem(10, 3) #=> 1(取余) # 严格布尔操作符:要求第一个参数必须是布尔值 true and true #=> true false or true #=> true # 1 and true #=> ** (BadBooleanError) expected a boolean on left-side of "and", got: 1 # 宽松逻辑操作符:接受任意类型,除 false 和 nil 外均视为真 1 || true #=> 1(短路返回第一个真值) false && 1 #=> false nil && 20 #=> nil !true #=> false比较操作符方面,教程特别指出===/!==在整型与浮点型比较时更严格:
1 == 1 #=> true 1 != 1 #=> false 1 < 2 #=> true 1 == 1.0 #=> true(宽松相等) 1 === 1.0 #=> false(严格相等,类型也须一致) 1 < :salut #=> true(可跨类型比较)最后一点值得展开:Elixir 为所有数据类型定义了全局全序——数字 < 原子 < 引用 < 函数 < 端口 < 进程 < 元组 < 列表 < 字符串。教程引用 Joe Armstrong 的话说:"实际顺序并不重要,重要的是全序被良好定义。"这意味着混合类型集合可以安全排序,为构建如["string", 123, :atom]这样的异构集合提供了底层保证。
控制流:基于模式匹配的分支家族
教程指出"Elixir 的许多控制结构都依赖于模式匹配",随后依次演示if、case、cond与try/catch:
# if 表达式(Elixir 中一切皆表达式,if 也有返回值) if false do "Aceasta nu veți vedea niciodată" else "Aceasta veți vedea" end # case:将值与多个模式依次比对 case {:unu, :doi} do {:patru, :cinci} -> "Aceasta nu se potrivește" {:unu, x} -> "Aceasta se potrivește și atribuie lui `x` `:doi` în acest bloc" _ -> "Aceasta se va potrivi cu orice valoare" end模式匹配中的下划线_被称为匿名变量(anonymous variable),用于占位那些不关心的值:
[cap | _] = [1,2,3] cap #=> 1 [cap | _coadă] = [:a, :b, :c] # 带下划线前缀的命名可提升可读性 cap #=> :a当分支条件多于两个时,教程推荐用cond取代嵌套的if,并以true作为兜底分支(等价于else):
cond do 1 + 1 == 3 -> "Aceasta nu veți vedea niciodată" 2 * 5 == 12 -> "Pe mine la fel" true -> "Aceasta veți vedea (este else în esență)" end异常投掷与捕获使用try/catch,其after子句无论是否捕获到值都会执行,类似其他语言的finally:
try do throw(:salut) catch mesaj -> "Am primit #{mesaj}." after IO.puts("Sunt în blocul after.") end #=> Sunt în blocul after. # "Am primit salut"模块与函数:匿名函数、守卫、递归与管道
匿名函数与守卫子句
匿名函数用fn ... end定义,调用时必须带点号f.(args):
square = fn(x) -> x * x end square.(5) #=> 25 # 支持多子句与守卫(guard),守卫用 when 关键字引入 f = fn x, y when x > 0 -> x + y x, y -> x * y end f.(1, 3) #=> 4 f.(-1, 3) #=> -3守卫是对模式匹配的精细化约束:当x > 0时执行加法,否则回退到乘法子句。Elixir 还内置了大量可直接调用的函数,如is_number/1、is_list/1、elem/2。
defmodule、def 与 defp
用defmodule将函数分组为模块,模块内用def定义公有函数、defp定义私有函数:
defmodule Math do def sum(a, b) do a + b end def square(x) do x * x end end Math.sum(1, 2) #=> 3 Math.square(3) #=> 9教程提示:把上述代码保存为math.ex后在终端执行elixirc math.ex即可编译(elixirc也是本仓库文档约定的编译入口命令)。私有函数的可见性边界清晰可见:
defmodule PrivateMath do def sum(a, b) do do_sum(a, b) # 公有函数可调用私有函数 end defp do_sum(a, b) do # 私有函数只能在模块内调用 a + b end end PrivateMath.sum(1, 2) #=> 3 PrivateMath.do_sum(1, 2) #=> ** (UndefinedFunctionError)函数定义同样支持守卫与多子句,调用时按定义顺序取第一个匹配成功的子句:
defmodule Geometry do def area({:rectangle, w, h}) do w * h end def area({:circle, r}) when is_number(r) do 3.14 * r * r end end Geometry.area({:rectangle, 2, 3}) #=> 6 Geometry.area({:circle, 3}) #=> 28.25999999999999801048 # Geometry.area({:circle, "not_a_number"}) #=> ** (FunctionClauseError) no function clause matching in Geometry.area/1注意:守卫失败不会抛出异常,而是导致"无匹配子句",最终抛出FunctionClauseError——这是函数式语言常见的错误形态,教程刻意保留了这一边界示例。
递归:不可变数据下的循环替代
由于变量不可变,循环在 Elixir 中由递归承担。教程用一个累加器(accumulator)模式的求和函数演示:
defmodule Recursion do def sum_list([head | tail], acc) do sum_list(tail, acc + head) # 尾递归:最后一件事是调用自身 end def sum_list([], acc) do acc # 空列表是递归出口 end end Recursion.sum_list([1,2,3], 0) #=> 6两个同名同参(arity 均为 2)的子句构成经典"空列表终止 + 非空递归"结构,这在后文的并发receive循环中还会再次出现。
模块属性与管道操作符
模块属性(module attribute)既是编译期常量,也是元数据载体:
defmodule MyMod do @moduledoc """ Este un atribut incorporat """ @my_data 100 # Acesta e atributul nostru IO.inspect(@my_data) #=> 100 end管道操作符|>是 Elixir 可读性的灵魂——它将左侧表达式的结果作为第一个参数传入右侧函数,让数据流自上而下:
Range.new(1,10) |> Enum.map(fn x -> x * x end) |> Enum.filter(fn x -> rem(x, 2) == 0 end) #=> [4, 16, 36, 64, 100]这段代码依次生成 1..10 区间、平方映射、过滤偶数,最终得到[4, 16, 36, 64, 100],将"数据流"思想表达得淋漓尽致。
结构体与异常处理
defstruct在映射之上构建结构体(struct),带来默认值、编译期字段检查与多态能力:
defmodule Person do defstruct name: nil, age: 0, height: 0 end joe_info = %Person{ name: "Joe", age: 30, height: 180 } #=> %Person{age: 30, height: 180, name: "Joe"} joe_info.name #=> "Joe"(点号访问字段) older_joe_info = %{ joe_info | age: 31 } #=> %Person{age: 31, height: 180, name: "Joe"}(更新语法:%{ struct | field: value })结构体更新的%{ struct | ... }语法会返回新结构体而保持原值不变,完美体现不可变语义。
异常处理用try/rescue,可按异常类型或通配符分支捕获:
try do raise "o eroare" rescue RuntimeError -> "a fost prinsă o eroare runtime" _error -> "aici vor fi prinse toate erorile" end #=> "a fost prinsă o eroare runtime" # 所有异常都有 message 字段 try do raise "o eroare" rescue x in [RuntimeError] -> x.message end #=> "o eroare"第二种写法用x in [RuntimeError]将异常绑定到变量x,从而访问x.message——这也是真实项目中提取错误信息的常用模式。
并发:Actor 模型三原语
教程断言:Elixir 的并发基于 Actor 模型,只需三个原语即可编写并发程序——创建进程(spawn)、发送消息(send)、接收消息(receive)。这份简洁正是 Erlang VM 二十年电信级并发工程的沉淀。
创建进程
f = fn -> 2 * 2 end #=> #Function<erl_eval.20.80484245> spawn(f) #=> #PID<0.40.0>spawn/1接收一个函数并立即返回进程标识符 pid(process identifier)。
发送与接收消息
defmodule Geometry do def area_loop do receive do {:rectangle, w, h} -> IO.puts("Aria = #{w * h}") area_loop() # 递归回到 receive,处理下一条消息 {:circle, r} -> IO.puts("Aria = #{3.14 * r * r}") area_loop() end end end pid = spawn(fn -> Geometry.area_loop() end) #=> #PID<0.40.0> pid = spawn(Geometry, :area_loop, []) # 等价写法:按模块/函数/参数列表派生 send pid, {:rectangle, 2, 3} #=> Aria = 6 # {:rectangle,2,3} send pid, {:circle, 2} #=> Aria = 12.56000000000000049738 # {:circle,2} self() #=> #PID<0.27.0>(iex Shell 本身也是一个进程)这段示例揭示了receive do的关键纪律:一个receive do块每次只处理一条消息,要持续处理就必须让函数递归调用自身回到receive。消息本身是任意 Elixir 值(此处是带原子标签的元组),接收端用模式匹配挑选感兴趣的消息——这与前文case的模式匹配机制一脉相承。self/0则返回当前进程的 pid,用于 Shell 与业务进程间的通信。
Agent:有状态的轻量进程封装
并发章节的收尾是 Agent——一个持续跟踪某个可变值的进程抽象,是 Elixir 官方Agent模块(基于 GenServer 的简化封装):
{ok, my_agent} = Agent.start_link(fn -> ["roșu", "verde"] end) # 初始状态 = 传入函数(fn -> ["roșu", "verde"] end)的返回值 Agent.get(my_agent, fn colors -> colors end) #=> ["roșu", "verde"] # get/2 把当前状态传给回调函数,回调的返回值即 get 的返回值 Agent.update(my_agent, fn colors -> ["albastru" | colors] end) # update/2 用回调的返回值替换状态:在列表头部插入 "albastru"Agent 的用法模式清晰:start_link/1初始化状态,get/2以函数式方式读取状态("读"不改变状态),update/2以函数式方式更新状态。所有操作都是纯函数作用于当前状态,天然适配不可变语义,是计数器、配置缓存等有状态场景的轻量选择。从实现角度看,Agent 背后是 BEAM 上标准的GenServer行为,教程借此为读者后续深入 OTP 打下铺垫。
延伸学习路径与仓库实践建议
原文档在代码块之外附有罗马尼亚语的"Link-uri utile"(有用链接)清单,包括 Elixir 官方入门指南、官方文档、Dave Thomas 的《Programming Elixir》、Fred Hebert 的《Learn You Some Erlang for Great Good!》以及 Joe Armstrong 的《Programming Erlang》等权威资源。结合本仓库,建议的实践路径是:
- 逐行运行:安装 Elixir 后,把 ro/elixir.md 代码块中的表达式逐段粘贴进
iex验证输出(#=>后即为预期结果); - 对照多语言版本:将罗马尼亚语版本与英文原版 elixir.md、中文版 zh-cn/elixir.md 对照阅读,可消除翻译差异带来的歧义(例如罗马尼亚语版用
spawn(Geometry, :area_loop, []),而中文旧版曾使用<-发送符号,应以send为准); - 动手编译:将
Math、Geometry模块存为.ex文件,用elixirc编译,体验"模块即编译单元"的工作流; - 深入并发:基于
Geometry.area_loop扩展多进程收发、进程链接与监督树,再过渡到 OTP 的 GenServer。
结语:一篇文档,一门语言的心智模型
ro/elixir.md 的价值不在于罗列语法,而在于用最少的文字串起 Elixir 的四根主线:模式匹配驱动的数据解构、不可变数据与递归、基于 Actor 的轻量进程并发、以及函数式状态管理。它同时示范了 Learn X in Y Minutes 项目"代码即文档"的创作范式——CONTRIBUTING.md 要求代码示例优先、行宽不超过 80 字符、全文 UTF-8 编码,这些约定由 lint/frontmatter.py 与 lint/encoding.sh 自动守护。当你把文中的每一行代码敲进iex并看到相同的输出时,你收获的不只是 Elixir 语法,更是一套面向并发与容错的编程思维方式。
- 文档
- 教程
【免费下载链接】learnxinyminutes-docs
Code documentation written as code! How novel and totally my idea!
相关推荐
Learn X in Y Minutes 式 Elixir 快速入门:从 BEAM 基础类型到 Actor 并发(learnxinyminutes-docs)
Learn X in Y Minutes 式 Elixir 快速入门:从 BEAM 基础类型到 Actor 并发(learnxinyminutes docs)
文档教程TinyUSB 版本发布全流程指南:从版本号提升、changelog 整理到 tag 落地
TinyUSB 版本发布全流程指南:从版本号提升、changelog 整理到 tag 落地 本篇技术指南围绕 TinyUSB 仓库内置的发布技能( make r
文档教程Python 2.7 快速入门:Learn X in Y Minutes 仓库 Python 2(legacy)教程深度解读
Python 2.7 快速入门:Learn X in Y Minutes 仓库 Python 2(legacy)教程深度解读 本文以 Learn X in Y
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考