查看源代码 Kernel (Elixir v1.16.2)
Kernel
是 Elixir 的默认环境。
它主要由以下组成:
- 基本语言原语,例如算术运算符、进程生成、数据类型处理等
- 用于控制流和定义新功能(模块、函数等)的宏
- 用于增强模式匹配的守卫检查
您可以在 Elixir 代码中的任何地方调用 Kernel
函数和宏,而无需使用 Kernel.
前缀,因为它们已自动导入。例如,在 IEx 中,您可以调用
iex> is_number(13)
true
如果您不想从 Kernel
导入函数或宏,请使用 :except
选项,然后按元数列出函数/宏
import Kernel, except: [if: 2, unless: 2]
有关导入的更多信息,请参阅 import/2
。
Elixir 还有一些始终导入且无法跳过的特殊形式。这些在 Kernel.SpecialForms
中有描述。
标准库
Kernel
提供了 Elixir 标准库构建的基础能力。建议您探索标准库以获得高级功能。以下是标准库中主要模块组(此列表并非完整参考,请参阅文档侧边栏以查看所有条目)。
内置类型
以下模块处理 Elixir 内置数据类型
Atom
- 具有名称的字面常量(true
、false
和nil
是原子)Float
- 带有浮点精度的数字Function
- 代码块的引用,使用fn/1
特殊形式创建Integer
- 整数(不是分数)List
- 可变数量元素的集合(链表)Map
- 键值对的集合Process
- 轻量级执行线程Port
- 与外部世界交互的机制Tuple
- 固定数量元素的集合
有两种没有伴随模块的数据类型
- Bitstring - 比特序列,使用
<<>>/1
创建。当比特数可以被 8 整除时,它们被称为二进制文件,可以使用 Erlang 的:binary
模块进行操作 - Reference - 运行时系统中的唯一值,使用
make_ref/0
创建
数据类型
Elixir 还提供其他数据类型,这些数据类型构建在上面列出的类型之上。其中一些是
Date
- 给定日历中的year-month-day
结构体DateTime
- 给定日历中的带有时区的日期和时间Exception
- 从错误和意外场景中引发的错误MapSet
- 唯一元素的无序集合NaiveDateTime
- 给定日历中的无时区的日期和时间Keyword
- 两元素元组列表,通常表示可选值Range
- 两个整数之间的包含范围Regex
- 正则表达式String
- 表示字符的 UTF-8 编码二进制文件Time
- 给定日历中的hour:minute:second
结构体URI
- 标识资源的 URI 表示形式Version
- 版本和要求的表示形式
系统模块
与底层系统交互的模块,例如
协议
协议将多态调度添加到 Elixir。它们是数据类型可以实现的契约。有关协议的更多信息,请参阅 Protocol
。Elixir 在标准库中提供以下协议
Collectable
- 将数据收集到数据类型中Enumerable
- 处理 Elixir 中的集合。Enum
模块提供了用于处理集合的急切函数,Stream
模块提供了惰性函数Inspect
- 将数据类型转换为其编程语言表示形式List.Chars
- 将数据类型转换为其外部世界表示形式,作为字符列表(非编程 based)String.Chars
- 将数据类型转换为其外部世界表示形式,作为字符串(非编程 based)
基于进程和以应用程序为中心的函数
以下模块构建在进程之上,以提供并发性、容错性和更多功能。
Agent
- 封装可变状态的进程Application
- 用于启动、停止和配置应用程序的函数GenServer
- 通用客户端-服务器 APIRegistry
- 基于进程的键值存储Supervisor
- 负责启动、监督和关闭其他进程的进程Task
- 执行计算的进程Task.Supervisor
- 用于专门管理任务的监管者
支持文档
在侧边栏的“页面”部分,您将找到教程、指南和参考文档,这些文档更详细地概述了 Elixir 语义和行为。它们是
- 兼容性和弃用 - 列出每个 Elixir 版本和 Erlang/OTP 之间的兼容性,发布模式;列出所有弃用的函数,以及它们何时被弃用以及替代方案
- 库指南 - 为编写库的人提供的一般指南、反模式和规则
- 命名约定 - Elixir 代码的命名约定
- 运算符参考 - 列出所有 Elixir 运算符及其优先级
- 模式和守卫 - 对模式、守卫和扩展的介绍
- 语法参考 - 语言语法参考
- 类型规范参考- 类型和函数规范,包括类型列表
- Unicode 语法 - 概述 Elixir 对 Unicode 的支持
守卫
此模块包含 Elixir 开发人员使用的内置守卫。它们是一组预定义的函数和宏,用于增强模式匹配,通常在 when
运算符之后调用。例如
def drive(%User{age: age}) when age >= 16 do
...
end
上面的子句只有在用户的年龄大于或等于 16 时才会被调用。守卫还支持使用 and
和 or
连接多个条件。如果所有守卫表达式都计算为 true
,则整个守卫为真。在 模式和守卫 页面中提供了对守卫的更完整介绍。
真值和假值
除了布尔值 true
和 false
之外,Elixir 还具有“真值”或“假值”的概念。
- 当值既不是
false
也不是nil
时,它是真值 - 当值是
false
或nil
时,它是假值
Elixir 有像 and/2
这样的函数,它们 *仅* 与布尔值一起工作,但也有像 &&/2
和 !/1
这样的函数,它们与这些真值/假值一起工作。
结构比较
此模块中的函数执行结构比较。这允许使用比较运算符比较不同的数据类型
iex> 1 < :an_atom
true
这是可能的,因此 Elixir 开发人员可以创建集合(如字典和有序集),这些集合在其中存储混合的数据类型。为了理解这为什么很重要,让我们讨论一下我们在软件中遇到的两种类型的比较:*结构* 和 *语义*。
结构意味着我们正在比较底层数据结构,并且我们通常希望这些操作尽可能快,因为它用于为语言中的几种算法和数据结构提供动力。语义比较关心每种数据类型代表什么。例如,从语义上讲,比较 Time
与 Date
是没有意义的。
一个显示结构比较和语义比较之间差异的例子是字符串:“alien” 排在“office” 之前 ("alien" < "office"
) 但“álien” 大于“office”。这是因为 <
比较形成字符串的底层字节。如果您要进行字母顺序排序,您可能希望“álien” 也出现在“office” 之前。
这意味着 **Elixir 中的比较是结构性的**,因为它旨在尽可能高效地比较数据类型,以创建灵活且高效的数据结构。这种区别对于提供排序的函数特别重要,例如 >/2
、</2
、>=/2
、<=/2
、min/2
和 max/2
。例如
~D[2017-03-31] > ~D[2017-04-01]
将返回 true
,因为结构比较在 :month
或 :year
之前比较 :day
字段。为了执行语义比较,相关数据类型提供了一个 compare/2
函数,例如 Date.compare/2
iex> Date.compare(~D[2017-03-31], ~D[2017-04-01])
:lt
或者,您可以使用 Enum
模块中的函数对数据进行排序或计算最大值/最小值
iex> Enum.sort([~D[2017-03-31], ~D[2017-04-01]], Date)
[~D[2017-03-31], ~D[2017-04-01]]
iex> Enum.max([~D[2017-03-31], ~D[2017-04-01]], Date)
~D[2017-04-01]
第二个参数正是用于语义比较的模块。保持这种区别很重要,因为如果语义比较默认用于实现数据结构和算法,它们可能会变得慢几个数量级!
最后,请注意有一个总的结构排序顺序,称为“项排序”,定义如下。此顺序是出于参考目的提供的,Elixir 开发人员不需要死记硬背。
术语排序
number < atom < reference < function < port < pid < tuple < map < list < bitstring
比较两个不同类型数字(数字可以是整数或浮点数)时,除非使用的比较运算符是 ===/2
或 !==
,否则将始终转换为精度更高的类型。浮点数将被认为比整数更精确,除非浮点数分别大于/小于 +/-9007199254740992.0,此时浮点数的所有有效数字都在小数点左侧。此行为的存在是为了使大数字的比较保持传递性。
集合类型使用以下规则进行比较
- 元组按大小比较,然后按元素逐个比较。
- 映射按大小比较,然后按升序排列的键比较,然后按键顺序比较值。在映射键排序的特定情况下,整数始终被认为小于浮点数。
- 列表按元素逐个比较。
- 位串按字节逐个比较,不完整的字节按位逐个比较。
- 原子使用其字符串值比较,逐个比较代码点。
示例
我们可以使用 !/1
函数两次来检查值的真值。
真值
iex> !!true
true
iex> !!5
true
iex> !![1,2]
true
iex> !!"foo"
true
假值(只有两个)
iex> !!false
false
iex> !!nil
false
内联
本模块中描述的一些函数由 Elixir 编译器内联到其在 :erlang
模块中的 Erlang 对应函数中。这些函数在 Erlang 中被称为 BIF(内置内部函数),它们表现出有趣的特性,因为其中一些函数允许在守卫中使用,另一些则用于编译器优化。
大多数内联函数可以在捕获函数时看到效果
iex> &Kernel.is_atom/1
&:erlang.is_atom/1
这些函数将在它们的文档中明确标记为“由编译器内联”。
总结
守卫
算术乘法运算符。
算术正号一元运算符。
算术加法运算符。
算术负号一元运算符。
算术减法运算符。
算术除法运算符。
不等于运算符。
严格不等于运算符。
小于运算符。
小于等于运算符。
等于运算符。如果两个项相等,则返回 true
。
严格等于运算符。
大于运算符。
大于等于运算符。
返回一个整数或浮点数,它是 number
的算术绝对值。
严格布尔“与”运算符。
提取二进制文件在 start
处大小为 size
的部分。
返回一个整数,表示 bitstring
的位大小。
返回包含 bitstring
所需的字节数。
返回大于或等于 number
的最小整数。
执行整数除法。
获取 tuple
中基于零的 index
处的元素。
返回小于或等于 number
的最大整数。
返回列表的头部。如果列表为空,则引发 ArgumentError
。
成员运算符。
如果 term
是一个原子,则返回 true
;否则返回 false
。
如果 term
是一个二进制文件,则返回 true
;否则返回 false
。
如果 term
是一个位串(包括二进制文件),则返回 true
;否则返回 false
。
如果 term
是原子 true
或原子 false
(即布尔值),则返回 true
;否则返回 false
。
如果 term
是一个异常,则返回 true
;否则返回 false
。
如果 term
是一个名为 name
的异常,则返回 true
;否则返回 false
。
如果 term
是一个浮点数,则返回 true
;否则返回 false
。
如果 term
是一个函数,则返回 true
;否则返回 false
。
如果 term
是一个可以用 arity
个参数调用的函数,则返回 true
;否则返回 false
。
如果 term
是一个整数,则返回 true
;否则返回 false
。
如果 term
是一个包含零个或多个元素的列表,则返回 true
;否则返回 false
。
如果 term
是一个映射,则返回 true
;否则返回 false
。
如果 key
是 map
中的键,则返回 true
;否则返回 false
。
如果 term
是 nil
,则返回 true
,否则返回 false
。
如果 term
是一个整数或一个浮点数,则返回 true
;否则返回 false
。
如果 term
是一个 PID(进程标识符),则返回 true
;否则返回 false
。
如果 term
是一个端口标识符,则返回 true
;否则返回 false
。
如果 term
是一个引用,则返回 true
;否则返回 false
。
如果 term
是一个结构体,则返回 true
;否则返回 false
。
如果 term
是一个名为 name
的结构体,则返回 true
;否则返回 false
。
如果 term
是一个元组,则返回 true
;否则返回 false
。
返回 list
的长度。
返回映射的大小。
返回一个表示本地节点名称的原子。如果节点不可用,则返回 :nonode@nohost
。
返回给定参数所在的节点。参数可以是 PID、引用或端口。如果本地节点不可用,则返回 :nonode@nohost
。
严格布尔“非”运算符。
严格布尔“或”运算符。
计算整数除法的余数。
将数字四舍五入到最接近的整数。
返回调用进程的 PID(进程标识符)。
返回列表的尾部。如果列表为空,则引发 ArgumentError
。
返回 number
的整数部分。
返回元组的大小。
函数
布尔“与”运算符。
幂运算符。
列表连接运算符。连接一个正确的列表和一个项,返回一个列表。
列表减法运算符。对右列表中的每个元素,删除左列表中第一个出现的元素。
创建完整切片范围 0..-1//1
。
从 first
到 last
创建一个范围。
从 first
到 last
创建一个步长为 step
的范围。
布尔“非”运算符。
二进制连接运算符。连接两个二进制文件。
基于文本的匹配运算符。将 left
上的项与 right
上的正则表达式或字符串进行匹配。
模块属性一元运算符。
在引号内使用时,表示给定的别名不应被卫生化。这意味着在宏扩展时将扩展该别名。
使用参数列表 args
调用给定的匿名函数 fun
。
使用参数列表 args
调用来自 module
的给定函数。
从范围开始处的偏移量返回一个二进制文件,到范围结束处的偏移量。
返回从偏移量 start
开始,大小为 size
的二进制文件。
以关键字列表的形式返回给定上下文的绑定。
使用给定的名称和主体定义一个公有函数。
定义一个委托给另一个模块的函数。
定义一个异常。
生成一个适合在守卫表达式中使用的宏。
生成一个适合在守卫表达式中使用的私有宏。
为给定的协议定义一个实现。
使用给定的名称和主体定义一个公有宏。
使用给定的名称和主体定义一个私有宏。
使用给定的名称和内容定义一个模块。
使当前模块中的给定定义可重写。
使用给定的名称和主体定义一个私有函数。
定义一个协议。
定义一个结构体。
解构两个列表,将右列表中的每个项分配给左列表中的匹配项。
使用给定的原因停止调用进程的执行。
如果 module
已加载,并且包含具有给定 arity
的公有 function
,则返回 true
,否则返回 false
。
通过给定的 path
获取值并更新嵌套数据结构。
获取嵌套结构中的值并更新它。
从嵌套结构中获取值。
提供一个 if/2
宏。
根据 Inspect
协议检查给定的参数。第二个参数是包含控制检查选项的关键字列表。
如果 module
已加载并包含具有给定 arity
的公共 macro
,则返回 true
,否则返回 false
。
返回一个几乎唯一的引用。
一个方便的宏,用于检查右侧(表达式)是否与左侧(模式)匹配。
根据结构比较返回两个给定项中最大的项。
根据结构比较返回两个给定项中最小的项。
通过给定的 path
从嵌套结构中弹出键。
从给定的嵌套结构中弹出键。
将 value
放置在 tuple
中给定的从零开始的 index
处。
通过给定的 path
将值放入嵌套结构中。
将值放入嵌套结构中。
引发异常。
引发异常并保留之前的堆栈跟踪。
引发异常并保留之前的堆栈跟踪。
将消息发送到给定的 dest
并返回该消息。
处理字符列表的 sigil ~C
。
处理字符列表的 sigil ~c
。
处理日期的 sigil ~D
。
处理朴素日期时间的 sigil ~N
。
处理正则表达式的 sigil ~r
。
处理字符串的 sigil ~S
。
处理字符串的 sigil ~s
。
处理时间的 sigil ~T
。
处理 sigil ~U
以创建 UTC DateTime
。
处理单词列表的 sigil ~W
。
处理单词列表的 sigil ~w
。
生成给定的函数并返回其 PID。
从给定的 module
生成给定的函数 fun
,传递给定的 args
并返回其 PID。
生成给定的函数,将其链接到当前进程,并返回其 PID。
从给定的 module
生成给定的函数 fun
,传递给定的 args
,将其链接到当前进程,并返回其 PID。
生成给定的函数,监控它并返回其 PID 和监控引用。
生成给定的模块和函数,传递给定的参数,监控它并返回其 PID 和监控引用。
创建和更新结构。
类似于 struct/2
,但会检查键的有效性。
将第一个参数 value
传递给第二个参数,一个函数 fun
,并返回 value
本身。
将第一个参数 value
传递给第二个参数,一个函数 fun
,并返回调用 fun
的结果。
从函数中进行非局部返回。
根据 List.Chars
协议将给定的项转换为字符列表。
根据 String.Chars
协议将参数转换为字符串。
提供一个 unless
宏。
通过给定的 path
更新嵌套结构。
更新嵌套结构中的键。
在当前上下文中使用给定的模块。
标记给定的变量不应被卫生化。
管道运算符。
布尔“或”运算符。
守卫
@spec integer() * integer() :: integer()
@spec float() * float() :: float()
@spec integer() * float() :: float()
@spec float() * integer() :: float()
算术乘法运算符。
允许在守卫测试中使用。由编译器内联。
示例
iex> 1 * 2
2
算术正号一元运算符。
允许在守卫测试中使用。由编译器内联。
示例
iex> +1
1
@spec integer() + integer() :: integer()
@spec float() + float() :: float()
@spec integer() + float() :: float()
@spec float() + integer() :: float()
算术加法运算符。
允许在守卫测试中使用。由编译器内联。
示例
iex> 1 + 2
3
@spec -0 :: 0
@spec -pos_integer() :: neg_integer()
@spec -neg_integer() :: pos_integer()
@spec -float() :: float()
算术负号一元运算符。
允许在守卫测试中使用。由编译器内联。
示例
iex> -2
-2
@spec integer() - integer() :: integer()
@spec float() - float() :: float()
@spec integer() - float() :: float()
@spec float() - integer() :: float()
算术减法运算符。
允许在守卫测试中使用。由编译器内联。
示例
iex> 1 - 2
-1
算术除法运算符。
结果始终是浮点数。如果您想要整数除法或余数,请使用 div/2
和 rem/2
。
如果 right
为 0 或 0.0,则引发 ArithmeticError
。
允许在守卫测试中使用。由编译器内联。
示例
1 / 2
#=> 0.5
-3.0 / 2.0
#=> -1.5
5 / 1
#=> 5.0
7 / 0
** (ArithmeticError) bad argument in arithmetic expression
不等于运算符。
如果两个项不相等,则返回 true
。
此运算符认为 1 和 1.0 相等。对于匹配比较,请改用 !==/2
。
这执行结构比较,其中所有 Elixir 项都可以相互比较。有关更多信息,请参阅 "结构比较" 部分 部分。
允许在守卫测试中使用。由编译器内联。
示例
iex> 1 != 2
true
iex> 1 != 1.0
false
严格不等于运算符。
如果两个项完全不相等,则返回 true
。请参阅 ===/2
以了解“完全相等”的定义。
这执行结构比较,其中所有 Elixir 项都可以相互比较。有关更多信息,请参阅 "结构比较" 部分 部分。
允许在守卫测试中使用。由编译器内联。
示例
iex> 1 !== 2
true
iex> 1 !== 1.0
true
小于运算符。
如果 left
小于 right
,则返回 true
。
这执行结构比较,其中所有 Elixir 项都可以相互比较。有关更多信息,请参阅 "结构比较" 部分 部分。
允许在守卫测试中使用。由编译器内联。
示例
iex> 1 < 2
true
小于等于运算符。
如果 left
小于或等于 right
,则返回 true
。
这执行结构比较,其中所有 Elixir 项都可以相互比较。有关更多信息,请参阅 "结构比较" 部分 部分。
允许在守卫测试中使用。由编译器内联。
示例
iex> 1 <= 2
true
等于运算符。如果两个项相等,则返回 true
。
此运算符认为 1 和 1.0 相等。对于更严格的语义,请改用 ===/2
。
这执行结构比较,其中所有 Elixir 项都可以相互比较。有关更多信息,请参阅 "结构比较" 部分 部分。
允许在守卫测试中使用。由编译器内联。
示例
iex> 1 == 2
false
iex> 1 == 1.0
true
严格等于运算符。
如果两个项完全相等,则返回 true
。
只有当两个项的值相同且类型相同,才认为它们完全相等。例如,1 == 1.0
返回 true
,但由于它们类型不同,1 === 1.0
返回 false
。
这执行结构比较,其中所有 Elixir 项都可以相互比较。有关更多信息,请参阅 "结构比较" 部分 部分。
允许在守卫测试中使用。由编译器内联。
示例
iex> 1 === 2
false
iex> 1 === 1.0
false
大于运算符。
如果 left
大于 right
,则返回 true
。
这执行结构比较,其中所有 Elixir 项都可以相互比较。有关更多信息,请参阅 "结构比较" 部分 部分。
允许在守卫测试中使用。由编译器内联。
示例
iex> 1 > 2
false
大于等于运算符。
如果 left
大于或等于 right
,则返回 true
。
这执行结构比较,其中所有 Elixir 项都可以相互比较。有关更多信息,请参阅 "结构比较" 部分 部分。
允许在守卫测试中使用。由编译器内联。
示例
iex> 1 >= 2
false
返回一个整数或浮点数,它是 number
的算术绝对值。
允许在守卫测试中使用。由编译器内联。
示例
iex> abs(-3.33)
3.33
iex> abs(-3)
3
严格布尔“与”运算符。
如果 left
为 false
,则返回 false
;否则返回 right
。
只需要 left
操作数为布尔值,因为它会短路。如果 left
操作数不是布尔值,则会引发 BadBooleanError
异常。
允许在守卫测试中使用。
示例
iex> true and false
false
iex> true and "yay!"
"yay!"
iex> "yay!" and true
** (BadBooleanError) expected a boolean on left-side of "and", got: "yay!"
@spec binary_part(binary(), non_neg_integer(), integer()) :: binary()
提取二进制文件在 start
处大小为 size
的部分。
如果 start
或 size
以任何方式引用二进制数据外部,则会引发 ArgumentError
异常。
允许在守卫测试中使用。由编译器内联。
示例
iex> binary_part("foo", 1, 2)
"oo"
可以使用负数 size
来提取位于 start
处字节之前的字节
iex> binary_part("Hello", 5, -3)
"llo"
当大小超出二进制数据范围时,会引发 ArgumentError
binary_part("Hello", 0, 10)
** (ArgumentError) argument error
@spec bit_size(bitstring()) :: non_neg_integer()
返回一个整数,表示 bitstring
的位大小。
允许在守卫测试中使用。由编译器内联。
示例
iex> bit_size(<<433::16, 3::3>>)
19
iex> bit_size(<<1, 2, 3>>)
24
@spec byte_size(bitstring()) :: non_neg_integer()
返回包含 bitstring
所需的字节数。
也就是说,如果 bitstring
中的位数不能被 8 整除,则结果的字节数将向上取整(取上限)。此操作在恒定时间内完成。
允许在守卫测试中使用。由编译器内联。
示例
iex> byte_size(<<433::16, 3::3>>)
3
iex> byte_size(<<1, 2, 3>>)
3
返回大于或等于 number
的最小整数。
如果您想对其他小数位进行向上取整操作,请改用 Float.ceil/2
。
允许在守卫测试中使用。由编译器内联。
示例
iex> ceil(10)
10
iex> ceil(10.1)
11
iex> ceil(-10.1)
-10
@spec div(integer(), neg_integer() | pos_integer()) :: integer()
执行整数除法。
如果其中一个参数不是整数,或者 divisor
为 0
,则会引发 ArithmeticError
异常。
div/2
执行截断整数除法。这意味着结果始终向零取整。
如果您想执行向下取整的整数除法(向负无穷大取整),请使用 Integer.floor_div/2
。
允许在守卫测试中使用。由编译器内联。
示例
div(5, 2)
#=> 2
div(6, -4)
#=> -1
div(-99, 2)
#=> -49
div(100, 0)
** (ArithmeticError) bad argument in arithmetic expression
@spec elem(tuple(), non_neg_integer()) :: term()
获取 tuple
中基于零的 index
处的元素。
当索引为负数或超出元组元素范围时,它会引发 ArgumentError
。
允许在守卫测试中使用。由编译器内联。
示例
tuple = {:foo, :bar, 3}
elem(tuple, 1)
#=> :bar
elem({}, 0)
** (ArgumentError) argument error
elem({:foo, :bar}, 2)
** (ArgumentError) argument error
返回小于或等于 number
的最大整数。
如果您想对其他小数位执行向下取整操作,请使用 Float.floor/2
。
允许在守卫测试中使用。由编译器内联。
示例
iex> floor(10)
10
iex> floor(9.7)
9
iex> floor(-9.7)
-10
@spec hd(nonempty_maybe_improper_list(elem, any())) :: elem when elem: term()
返回列表的头部。如果列表为空,则引发 ArgumentError
。
列表的头部是它的第一个元素。
它适用于不完整的列表。
允许在守卫测试中使用。由编译器内联。
示例
hd([1, 2, 3, 4])
#=> 1
hd([1 | 2])
#=> 1
如果给它一个空列表,它将引发
hd([])
** (ArgumentError) argument error
成员运算符。
检查左侧的元素是否为右侧集合的成员。
示例
iex> x = 1
iex> x in [1, 2, 3]
true
这个运算符(是一个宏)仅仅转换为对 Enum.member?/2
的调用。上面的例子将转换为
Enum.member?([1, 2, 3], x)
Elixir 也支持 left not in right
,它评估为 not(left in right)
iex> x = 1
iex> x not in [1, 2, 3]
false
保护
in/2
运算符(以及 not in
)可以在保护子句中使用,只要右侧是范围或列表。
如果右侧是一个列表,Elixir 将把运算符扩展为一个有效的保护表达式,该表达式需要检查每个值。例如
when x in [1, 2, 3]
转换为
when x === 1 or x === 2 or x === 3
但是,这种结构对于大型列表来说效率低下。在这种情况下,最好停止使用保护,并使用更合适的数据结构,例如 MapSet
。
如果右侧是范围,则将执行更有效的比较检查。例如
when x in 1..1000
大约转换为
when x >= 1 and x <= 1000
AST 考虑因素
left not in right
由编译器解析为 AST
{:not, _, [{:in, _, [left, right]}]}
这与 not(left in right)
的 AST 相同。
此外,Macro.to_string/2
和 Code.format_string!/2
将把所有此 AST 的出现转换为 left not in right
。
如果 term
是一个原子,则返回 true
;否则返回 false
。
允许在守卫测试中使用。由编译器内联。
示例
iex> is_atom(false)
true
iex> is_atom(:name)
true
iex> is_atom(AnAtom)
true
iex> is_atom("true")
false
如果 term
是一个二进制文件,则返回 true
;否则返回 false
。
二进制总是包含完整数量的字节。
允许在守卫测试中使用。由编译器内联。
示例
iex> is_binary("foo")
true
iex> is_binary(<<1::3>>)
false
如果 term
是一个位串(包括二进制文件),则返回 true
;否则返回 false
。
允许在守卫测试中使用。由编译器内联。
示例
iex> is_bitstring("foo")
true
iex> is_bitstring(<<1::3>>)
true
如果 term
是原子 true
或原子 false
(即布尔值),则返回 true
;否则返回 false
。
允许在守卫测试中使用。由编译器内联。
示例
iex> is_boolean(false)
true
iex> is_boolean(true)
true
iex> is_boolean(:test)
false
如果 term
是一个异常,则返回 true
;否则返回 false
。
允许在守卫测试中使用。
示例
iex> is_exception(%RuntimeError{})
true
iex> is_exception(%{})
false
如果 term
是一个名为 name
的异常,则返回 true
;否则返回 false
。
允许在守卫测试中使用。
示例
iex> is_exception(%RuntimeError{}, RuntimeError)
true
iex> is_exception(%RuntimeError{}, Macro.Env)
false
如果 term
是一个浮点数,则返回 true
;否则返回 false
。
允许在守卫测试中使用。由编译器内联。
如果 term
是一个函数,则返回 true
;否则返回 false
。
允许在守卫测试中使用。由编译器内联。
示例
iex> is_function(fn x -> x + x end)
true
iex> is_function("not a function")
false
@spec is_function(term(), non_neg_integer()) :: boolean()
如果 term
是一个可以用 arity
个参数调用的函数,则返回 true
;否则返回 false
。
允许在守卫测试中使用。由编译器内联。
示例
iex> is_function(fn x -> x * 2 end, 1)
true
iex> is_function(fn x -> x * 2 end, 2)
false
如果 term
是一个整数,则返回 true
;否则返回 false
。
允许在守卫测试中使用。由编译器内联。
如果 term
是一个包含零个或多个元素的列表,则返回 true
;否则返回 false
。
允许在守卫测试中使用。由编译器内联。
如果 term
是一个映射,则返回 true
;否则返回 false
。
允许在守卫测试中使用。由编译器内联。
如果 key
是 map
中的键,则返回 true
;否则返回 false
。
如果第一个元素不是映射,则会引发 BadMapError
。
允许在守卫测试中使用。由编译器内联。
示例
iex> is_map_key(%{a: "foo", b: "bar"}, :a)
true
iex> is_map_key(%{a: "foo", b: "bar"}, :c)
false
如果 term
是 nil
,则返回 true
,否则返回 false
。
在保护子句中允许。
示例
iex> is_nil(1)
false
iex> is_nil(nil)
true
如果 term
是一个整数或一个浮点数,则返回 true
;否则返回 false
。
允许在守卫测试中使用。由编译器内联。
如果 term
是一个 PID(进程标识符),则返回 true
;否则返回 false
。
允许在守卫测试中使用。由编译器内联。
如果 term
是一个端口标识符,则返回 true
;否则返回 false
。
允许在守卫测试中使用。由编译器内联。
如果 term
是一个引用,则返回 true
;否则返回 false
。
允许在守卫测试中使用。由编译器内联。
如果 term
是一个结构体,则返回 true
;否则返回 false
。
允许在守卫测试中使用。
示例
iex> is_struct(URI.parse("/"))
true
iex> is_struct(%{})
false
如果 term
是一个名为 name
的结构体,则返回 true
;否则返回 false
。
is_struct/2
不检查 name
是否存在以及是否是一个有效的结构体。如果您需要这样的验证,您必须改为对结构体进行模式匹配,例如 match?(%URI{}, arg)
。
允许在守卫测试中使用。
示例
iex> is_struct(URI.parse("/"), URI)
true
iex> is_struct(URI.parse("/"), Macro.Env)
false
如果 term
是一个元组,则返回 true
;否则返回 false
。
允许在守卫测试中使用。由编译器内联。
@spec length(list()) :: non_neg_integer()
返回 list
的长度。
允许在守卫测试中使用。由编译器内联。
示例
iex> length([1, 2, 3, 4, 5, 6, 7, 8, 9])
9
@spec map_size(map()) :: non_neg_integer()
返回映射的大小。
映射的大小是映射中包含的键值对的数量。
此操作在恒定时间内发生。
允许在守卫测试中使用。由编译器内联。
示例
iex> map_size(%{a: "foo", b: "bar"})
2
@spec node() :: node()
返回一个表示本地节点名称的原子。如果节点不可用,则返回 :nonode@nohost
。
允许在守卫测试中使用。由编译器内联。
返回给定参数所在的节点。参数可以是 PID、引用或端口。如果本地节点不可用,则返回 :nonode@nohost
。
允许在守卫测试中使用。由编译器内联。
@spec not true :: false
@spec not false :: true
严格布尔“非”运算符。
value
必须是布尔值;如果不是,将引发 ArgumentError
异常。
允许在守卫测试中使用。由编译器内联。
示例
iex> not false
true
严格布尔“或”运算符。
如果 left
为 true
,则返回 true
;否则返回 right
。
只需要 left
操作数为布尔值,因为它会短路。如果 left
操作数不是布尔值,则会引发 BadBooleanError
异常。
允许在守卫测试中使用。
示例
iex> true or false
true
iex> false or 42
42
iex> 42 or false
** (BadBooleanError) expected a boolean on left-side of "or", got: 42
@spec rem(integer(), neg_integer() | pos_integer()) :: integer()
计算整数除法的余数。
rem/2
使用截断除法,这意味着结果将始终具有 dividend
的符号。
如果其中一个参数不是整数,或者 divisor
为 0
,则会引发 ArithmeticError
异常。
允许在守卫测试中使用。由编译器内联。
示例
iex> rem(5, 2)
1
iex> rem(6, -4)
2
将数字四舍五入到最接近的整数。
如果该数字与两个最接近的整数等距,则舍入到零。
允许在守卫测试中使用。由编译器内联。
示例
iex> round(5.6)
6
iex> round(5.2)
5
iex> round(-9.9)
-10
iex> round(-9)
-9
iex> round(2.5)
3
iex> round(-2.5)
-3
@spec self() :: pid()
返回调用进程的 PID(进程标识符)。
在保护子句中允许。由编译器内联。
@spec tl(nonempty_maybe_improper_list(elem, last)) :: maybe_improper_list(elem, last) | last when elem: term(), last: term()
返回列表的尾部。如果列表为空,则引发 ArgumentError
。
列表的尾部是去除第一个元素后的列表。
它适用于不完整的列表。
允许在守卫测试中使用。由编译器内联。
示例
tl([1, 2, 3, :go])
#=> [2, 3, :go]
tl([:one])
#=> []
tl([:a, :b | :improper_end])
#=> [:b | :improper_end]
tl([:a | %{b: 1}])
#=> %{b: 1}
如果给它一个空列表,它将引发
tl([])
** (ArgumentError) argument error
返回 number
的整数部分。
允许在守卫测试中使用。由编译器内联。
示例
iex> trunc(5.4)
5
iex> trunc(-5.99)
-5
iex> trunc(-5)
-5
@spec tuple_size(tuple()) :: non_neg_integer()
返回元组的大小。
此操作在恒定时间内发生。
允许在守卫测试中使用。由编译器内联。
示例
iex> tuple_size({:a, :b, :c})
3
函数
布尔“与”运算符。
提供一个短路运算符,仅当第一个表达式计算为真值(既不是 false
也不是 nil
)时才计算和返回第二个表达式。否则返回第一个表达式。
在保护子句中不允许。
示例
iex> Enum.empty?([]) && Enum.empty?([])
true
iex> List.first([]) && true
nil
iex> Enum.empty?([]) && List.first([1])
1
iex> false && throw(:bad)
false
注意,与 and/2
不同,此运算符接受任何表达式作为第一个参数,而不仅仅是布尔值。
@spec integer() ** non_neg_integer() :: integer()
@spec integer() ** neg_integer() :: float()
@spec float() ** float() :: float()
@spec integer() ** float() :: float()
@spec float() ** integer() :: float()
幂运算符。
它接收两个数字作为输入。如果两个数字都是整数,并且右侧(exponent
)也大于或等于 0,则结果也将是整数。否则,它将返回一个浮点数。
示例
iex> 2 ** 2
4
iex> 2 ** -4
0.0625
iex> 2.0 ** 2
4.0
iex> 2 ** 2.0
4.0
@spec list() ++ term() :: maybe_improper_list()
列表连接运算符。连接一个正确的列表和一个项,返回一个列表。
a ++ b
的复杂度与 length(a)
成正比,因此避免对任意长度的列表进行重复追加,例如 list ++ [element]
。相反,考虑通过 [element | rest]
进行预置,然后反转。
如果 right
操作数不是一个完整的列表,它将返回一个不完整的列表。如果 left
操作数不是一个完整的列表,它将引发 ArgumentError
。
由编译器内联。
示例
iex> [1] ++ [2, 3]
[1, 2, 3]
iex> ~c"foo" ++ ~c"bar"
~c"foobar"
# a non-list on the right will return an improper list
# with said element at the end
iex> [1, 2] ++ 3
[1, 2 | 3]
iex> [1, 2] ++ {3, 4}
[1, 2 | {3, 4}]
# improper list on the right will return an improper list
iex> [1] ++ [2 | 3]
[1, 2 | 3]
++/2
运算符是右结合的,这意味着
iex> [1, 2, 3] -- [1] ++ [2]
[3]
因为它等效于
iex> [1, 2, 3] -- ([1] ++ [2])
[3]
列表减法运算符。对右列表中的每个元素,删除左列表中第一个出现的元素。
此函数经过优化,因此 a -- b
的复杂度与 length(a) * log(length(b))
成正比。另请参阅 Erlang 效率指南。
由编译器内联。
示例
iex> [1, 2, 3] -- [1, 2]
[3]
iex> [1, 2, 3, 2, 1] -- [1, 2, 2]
[3, 1]
--/2
运算符是右结合的,这意味着
iex> [1, 2, 3] -- [2] -- [3]
[1, 3]
因为它等效于
iex> [1, 2, 3] -- ([2] -- [3])
[1, 3]
创建完整切片范围 0..-1//1
。
此宏返回具有以下属性的范围
枚举时,它是空的
当用作
slice
时,它将按原样返回切片元素
有关更多信息,请参见 ..///3
以及 Range
模块。
示例
iex> Enum.to_list(..)
[]
iex> String.slice("Hello world!", ..)
"Hello world!"
从 first
到 last
创建一个范围。
如果 first 小于 last,则范围将从 first 增加到 last。如果 first 等于 last,则范围将包含一个元素,即该数字本身。
如果 first 大于 last,则范围将从 first 减少到 last,尽管这种行为已弃用。相反,请使用 first..last//-1
明确列出步长。
有关更多信息,请参见 Range
模块。
示例
iex> 0 in 1..3
false
iex> 2 in 1..3
true
iex> Enum.to_list(1..3)
[1, 2, 3]
从 first
到 last
创建一个步长为 step
的范围。
有关更多信息,请参见 Range
模块。
示例
iex> 0 in 1..3//1
false
iex> 2 in 1..3//1
true
iex> 2 in 1..3//2
false
iex> Enum.to_list(1..3//1)
[1, 2, 3]
iex> Enum.to_list(1..3//2)
[1, 3]
iex> Enum.to_list(3..1//-1)
[3, 2, 1]
iex> Enum.to_list(1..0//1)
[]
布尔“非”运算符。
接收任何值(不仅仅是布尔值),如果 value
是 false
或 nil
,则返回 true
;否则返回 false
。
在保护子句中不允许。
示例
iex> !Enum.empty?([])
false
iex> !List.first([])
true
二进制连接运算符。连接两个二进制文件。
如果其中一方不是二进制文件,则引发 ArgumentError
。
示例
iex> "foo" <> "bar"
"foobar"
只要左侧参数是文字二进制文件,<>/2
运算符也可以用于模式匹配(以及守卫子句)。
iex> "foo" <> x = "foobar"
iex> x
"bar"
x <> "bar" = "foobar"
将导致 ArgumentError
异常。
基于文本的匹配运算符。将 left
上的项与 right
上的正则表达式或字符串进行匹配。
如果 right
是正则表达式,则如果 left
与 right 匹配,则返回 true
。
如果 right
是字符串,则如果 left
包含 right
,则返回 true
。
示例
iex> "abcd" =~ ~r/c(d)/
true
iex> "abcd" =~ ~r/e/
false
iex> "abcd" =~ ~r//
true
iex> "abcd" =~ "bc"
true
iex> "abcd" =~ "ad"
false
iex> "abcd" =~ "abcd"
true
iex> "abcd" =~ ""
true
有关正则表达式的更多信息,请查看 Regex
模块。
模块属性一元运算符。
读取和写入当前模块中的属性。
属性的典型示例是注释模块实现 OTP 行为,例如 GenServer
defmodule MyServer do
@behaviour GenServer
# ... callbacks ...
end
默认情况下,Elixir 支持 Erlang 支持的所有模块属性,但也可以使用自定义属性。
defmodule MyServer do
@my_data 13
IO.inspect(@my_data)
#=> 13
end
与 Erlang 不同,这些属性默认情况下不会存储在模块中,因为在 Elixir 中,通常使用自定义属性来存储将在编译时可用的临时数据。可以使用 Module.register_attribute/3
将自定义属性配置为更接近 Erlang 的行为。
模块属性前缀
库和框架应考虑将任何私有模块属性前缀为下划线,例如
@_my_data
,以便代码完成工具不会在建议和提示中显示它们。
最后,请注意,属性也可以在函数内部读取。
defmodule MyServer do
@my_data 11
def first_data, do: @my_data
@my_data 13
def second_data, do: @my_data
end
MyServer.first_data()
#=> 11
MyServer.second_data()
#=> 13
重要的是要注意,读取属性会获取其当前值的快照。换句话说,该值是在编译时读取的,而不是在运行时读取的。有关操作模块属性的其他函数,请查看 Module
模块。
注意!同一属性的多个引用
如上所述,每次读取模块属性时,都会获取其当前值的快照。因此,如果您在模块属性中存储大量值(例如,将外部文件嵌入模块属性中),则应避免多次引用同一属性。例如,不要这样做
@files %{
example1: File.read!("lib/example1.data"),
example2: File.read!("lib/example2.data")
}
def example1, do: @files[:example1]
def example2, do: @files[:example2]
在上面,对 @files
的每次引用最终都可能导致整个 @files
模块属性的完整独立副本。相反,在私有函数中引用一次模块属性
@files %{
example1: File.read!("lib/example1.data"),
example2: File.read!("lib/example2.data")
}
defp files(), do: @files
def example1, do: files()[:example1]
def example2, do: files()[:example2]
注意!编译时依赖项
请记住,即使在模块属性中,对其他模块的引用也会生成对这些模块的编译时依赖项。
例如,考虑这种常见的模式
@values [:foo, :bar, :baz]
def handle_arg(arg) when arg in @values do
...
end
虽然以上内容没问题,但想象一下,如果在模块属性中实际使用的是模块名称,例如
@values [Foo, Bar, Baz]
def handle_arg(arg) when arg in @values do
...
end
上面的代码将对模块 Foo
、Bar
和 Baz
定义编译时依赖项,这样,如果它们中的任何一个发生更改,当前模块就必须重新编译。在这种情况下,最好完全避免使用模块属性。
def handle_arg(arg) when arg in [Foo, Bar, Baz] do
...
end
在引号内使用时,表示给定的别名不应被卫生化。这意味着在宏扩展时将扩展该别名。
有关更多信息,请查看 quote/2
。
使用参数列表 args
调用给定的匿名函数 fun
。
如果参数数量在编译时已知,则优先使用 fun.(arg_1, arg_2, ..., arg_n)
,因为它比 apply(fun, [arg_1, arg_2, ..., arg_n])
更清晰。
由编译器内联。
示例
iex> apply(fn x -> x * 2 end, [2])
4
使用参数列表 args
调用来自 module
的给定函数。
apply/3
用于调用函数,其中模块、函数名称或参数在运行时动态定义。因此,您不能使用 apply/3
调用宏,只能调用函数。
如果参数数量和函数名称在编译时已知,则优先使用 module.function(arg_1, arg_2, ..., arg_n)
,因为它比 apply(module, :function, [arg_1, arg_2, ..., arg_n])
更清晰。
apply/3
不能用于调用私有函数。
由编译器内联。
示例
iex> apply(Enum, :reverse, [[1, 2, 3]])
[3, 2, 1]
从范围开始处的偏移量返回一个二进制文件,到范围结束处的偏移量。
如果范围的开始或结束为负数,则它们将根据二进制文件的大小转换为正索引。例如,-1
表示二进制文件的最后一个字节。
这类似于 binary_part/3
,只是它使用范围,并且在守卫中不允许。
此函数使用字节。对于考虑字符的切片操作,请参见 String.slice/2
。
示例
iex> binary_slice("elixir", 0..5)
"elixir"
iex> binary_slice("elixir", 1..3)
"lix"
iex> binary_slice("elixir", 1..10)
"lixir"
iex> binary_slice("elixir", -4..-1)
"ixir"
iex> binary_slice("elixir", -4..6)
"ixir"
iex> binary_slice("elixir", -10..10)
"elixir"
对于 start > stop
的范围,您需要明确地将其标记为递增。
iex> binary_slice("elixir", 2..-1//1)
"ixir"
iex> binary_slice("elixir", 1..-2//1)
"lixi"
您可以使用 ../0
作为 0..-1//1
的快捷方式,它将返回完整的二进制文件。
iex> binary_slice("elixir", ..)
"elixir"
步长可以是任何正数。例如,要获取二进制文件的每 2 个字符
iex> binary_slice("elixir", 0..-1//2)
"eii"
如果第一个位置在字符串结尾之后或在范围的最后一个位置之后,则它将返回一个空字符串。
iex> binary_slice("elixir", 10..3//1)
""
iex> binary_slice("elixir", -10..-7)
""
iex> binary_slice("a", 1..1500)
""
返回从偏移量 start
开始,大小为 size
的二进制文件。
这类似于 binary_part/3
,只是如果 start + size
大于二进制文件大小,它会自动将其剪切到二进制文件大小,而不是引发异常。与 binary_part/3
相反,此函数在守卫中不允许。
此函数使用字节。对于考虑字符的切片操作,请参见 String.slice/3
。
示例
iex> binary_slice("elixir", 0, 6)
"elixir"
iex> binary_slice("elixir", 0, 5)
"elixi"
iex> binary_slice("elixir", 1, 4)
"lixi"
iex> binary_slice("elixir", 0, 10)
"elixir"
如果 start
为负数,则会根据二进制文件大小进行标准化,并钳位到 0。
iex> binary_slice("elixir", -3, 10)
"xir"
iex> binary_slice("elixir", -10, 10)
"elixir"
如果 size
为零,则返回一个空二进制文件。
iex> binary_slice("elixir", 1, 0)
""
如果 start
大于或等于二进制文件大小,则返回一个空二进制文件。
iex> binary_slice("elixir", 10, 10)
""
以关键字列表的形式返回给定上下文的绑定。
在返回的结果中,键是变量名称,值是相应的变量值。
如果给定的 context
为 nil
(默认情况下为),则返回当前上下文的绑定。
示例
iex> x = 1
iex> binding()
[x: 1]
iex> x = 2
iex> binding()
[x: 2]
iex> binding(:foo)
[]
iex> var!(x, :foo) = 1
1
iex> binding(:foo)
[x: 1]
调试给定的 code
。
dbg/2
可用于通过可配置的调试函数调试给定的 code
。它返回给定代码的结果。
示例
让我们来看一下对 dbg/2
的调用
dbg(Atom.to_string(:debugging))
#=> "debugging"
它返回字符串 "debugging"
,这是 Atom.to_string/1
调用的结果。此外,上面的调用还打印以下内容
[my_file.ex:10: MyMod.my_fun/0]
Atom.to_string(:debugging) #=> "debugging"
默认的调试函数在处理管道时会打印其他调试信息。它会打印管道中每个“步骤”的值。
"Elixir is cool!"
|> String.trim_trailing("!")
|> String.split()
|> List.first()
|> dbg()
#=> "Elixir"
上面的代码打印以下内容
[my_file.ex:10: MyMod.my_fun/0]
"Elixir is cool!" #=> "Elixir is cool!"
|> String.trim_trailing("!") #=> "Elixir is cool"
|> String.split() #=> ["Elixir", "is", "cool"]
|> List.first() #=> "Elixir"
不带参数,dbg()
会调试有关当前绑定的信息。请参见 binding/1
。
dbg
在 IEx 中
您可以通过调用以下内容来启用 IEx 将 dbg
替换为其 IEx.pry/0
后端
$ iex --dbg pry
在这种情况下,dbg
将启动一个 pry
会话,您可以在其中与当前环境中 dbg
调用位置的导入、别名和变量进行交互。
如果您在 IEx 中使用管道(使用 |>
)在管道末尾调用 dbg
,则可以通过输入“next”(或“n”)逐个执行管道的每个步骤。
请注意,dbg
只支持对管道的单步执行(换句话说,它只能单步执行它看到的代码)。对于一般单步执行,您可以使用 IEx.break!/4
设置断点。
有关更多信息,请 参阅 IEx 文档。
配置调试函数
dbg/2
的优点之一是,它的调试逻辑是可配置的,这使工具能够通过增强的行为来扩展 dbg
。例如,IEx
通过交互式 shell 扩展了 dbg
,您可以在其中直接检查和访问值。
调试函数可以通过 :elixir
应用程序的 :dbg_callback
键在编译时进行配置。调试函数必须是一个 {module, function, args}
元组。module
中的 function
函数将被调用,其参数为 prepended 到 args
调试函数在编译时被调用,并且它也必须返回一个 AST。预期 AST 最终会返回调试表达式求值的結果。
这是一个简单的例子
defmodule MyMod do
def debug_fun(code, options, caller, device) do
quote do
result = unquote(code)
IO.inspect(unquote(device), result, label: unquote(Macro.to_string(code)))
end
end
end
要配置调试函数
# In config/config.exs
config :elixir, :dbg_callback, {MyMod, :debug_fun, [:stdio]}
默认调试函数
默认情况下,我们使用的调试函数是 Macro.dbg/3
。它只是将有关代码的信息打印到标准输出并返回对 code
求值后返回的值。 options
用于控制术语的检查方式。它们是 inspect/2
接受的相同选项。
使用给定的名称和主体定义一个公有函数。
例子
defmodule Foo do
def bar, do: :baz
end
Foo.bar()
#=> :baz
可以按如下方式定义一个期望参数的函数
defmodule Foo do
def sum(a, b) do
a + b
end
end
在上面的例子中,定义了一个 sum/2
函数;该函数接收两个参数并返回它们的和。
默认参数
\\
用于为函数参数指定默认值。例如
defmodule MyMath do
def multiply_by(number, factor \\ 2) do
number * factor
end
end
MyMath.multiply_by(4, 3)
#=> 12
MyMath.multiply_by(4)
#=> 8
编译器将此转换为具有不同元数的多个函数,这里有 MyMath.multiply_by/1
和 MyMath.multiply_by/2
,它们分别表示为具有默认值的参数传递或未传递参数的情况。
在定义具有默认参数和多个显式声明的子句的函数时,必须编写一个声明默认值的函数头。例如
defmodule MyString do
def join(string1, string2 \\ nil, separator \\ " ")
def join(string1, nil, _separator) do
string1
end
def join(string1, string2, separator) do
string1 <> separator <> string2
end
end
请注意,\\
不能与匿名函数一起使用,因为它们只能有一个元数。
具有默认参数的关键字列表
包含许多参数的函数可以通过使用 Keyword
列表将属性分组并作为一个值传递来获益。
defmodule MyConfiguration do
@default_opts [storage: "local"]
def configure(resource, opts \\ []) do
opts = Keyword.merge(@default_opts, opts)
storage = opts[:storage]
# ...
end
end
使用 Map
和 Keyword
来存储许多参数之间的区别在于 Keyword
的键
- 必须是原子
- 可以多次给出
- 按开发人员指定的顺序排列
函数名
Elixir 中的函数和变量名必须以下划线或非大写或标题大小写的 Unicode 字母开头。它们可以继续使用 Unicode 字母、数字和下划线的序列。它们可以以 ?
或 !
结尾。Elixir 的 命名约定 建议函数和变量名以 snake_case
格式编写。
rescue
/catch
/after
/else
函数体支持 rescue
、catch
、after
和 else
,就像 try/1
一样(称为“隐式尝试”)。例如,以下两个函数是等效的
def convert(number) do
try do
String.to_integer(number)
rescue
e in ArgumentError -> {:error, e.message}
end
end
def convert(number) do
String.to_integer(number)
rescue
e in ArgumentError -> {:error, e.message}
end
定义一个委托给另一个模块的函数。
使用 defdelegate/2
定义的函数是公开的,可以从定义它们的模块之外调用,就像使用 def/2
定义的一样。因此,defdelegate/2
是关于扩展当前模块的公共 API。如果您想在不使用完整模块名的情况下调用另一个模块中定义的函数,则可以使用 alias/2
来缩短模块名,或者使用 import/2
来完全不使用模块名就可以调用该函数。
委托仅适用于函数;不支持委托宏。
查看 def/2
以了解有关命名和默认参数的规则。
选项
:to
- 要分派的模块。:as
- 要在:to
中给定的目标上调用的函数。此参数是可选的,默认值为委托的名称(funs
)。
例子
defmodule MyList do
defdelegate reverse(list), to: Enum
defdelegate other_reverse(list), to: Enum, as: :reverse
end
MyList.reverse([1, 2, 3])
#=> [3, 2, 1]
MyList.other_reverse([1, 2, 3])
#=> [3, 2, 1]
定义一个异常。
异常是通过实现 Exception
行为的模块支持的结构。 Exception
行为要求实现两个函数
exception/1
- 接收传递给raise/2
的参数并返回异常结构。默认实现接受一组关键字参数,这些参数将合并到结构中,或接受一个用作异常消息的字符串。message/1
- 接收异常结构,并且必须返回其消息。最常见的异常有一个消息字段,默认情况下通过此函数访问。但是,如果异常没有消息字段,则必须显式实现此函数。
由于异常是结构,因此 defstruct/1
支持的 API 也在 defexception/1
中可用。
引发异常
引发异常最常见的方法是通过 raise/2
defmodule MyAppError do
defexception [:message]
end
value = [:hello]
raise MyAppError,
message: "did not get what was expected, got: #{inspect(value)}"
在许多情况下,将预期值传递给 raise/2
并生成消息在 Exception.exception/1
回调中更为方便
defmodule MyAppError do
defexception [:message]
@impl true
def exception(value) do
msg = "did not get what was expected, got: #{inspect(value)}"
%MyAppError{message: msg}
end
end
raise MyAppError, value
上面的例子展示了自定义异常消息的首选策略。
生成一个适合在守卫表达式中使用的宏。
如果定义使用不在保护范围内的表达式,它会在编译时引发异常,否则它会创建一个可以在保护范围内部或外部使用的宏。
请注意,Elixir 中的约定是在返回布尔值的保护符之前添加 is_
前缀,例如 is_list/1
。但是,如果函数/宏返回布尔值并且不在保护范围内,则它不应该有任何前缀,并且应该以问号结尾,例如 Keyword.keyword?/1
。
例子
defmodule Integer.Guards do
defguard is_even(value) when is_integer(value) and rem(value, 2) == 0
end
defmodule Collatz do
@moduledoc "Tools for working with the Collatz sequence."
import Integer.Guards
@doc "Determines the number of steps `n` takes to reach `1`."
# If this function never converges, please let me know what `n` you used.
def converge(n) when n > 0, do: step(n, 0)
defp step(1, step_count) do
step_count
end
defp step(n, step_count) when is_even(n) do
step(div(n, 2), step_count + 1)
end
defp step(n, step_count) do
step(3 * n + 1, step_count + 1)
end
end
生成一个适合在守卫表达式中使用的私有宏。
如果定义使用不在保护范围内的表达式,它会在编译时引发异常,否则它会创建一个可以在当前模块的保护范围内部或外部使用的私有宏。
与 defmacrop/2
类似,defguardp/1
必须在其在当前模块中使用之前定义。
为给定的协议定义一个实现。
有关更多信息,请参阅 Protocol
模块。
使用给定的名称和主体定义一个公有宏。
宏必须在其使用之前定义。
查看 def/2
以了解有关命名和默认参数的规则。
例子
defmodule MyLogic do
defmacro unless(expr, opts) do
quote do
if !unquote(expr), unquote(opts)
end
end
end
require MyLogic
MyLogic.unless false do
IO.puts("It works")
end
使用给定的名称和主体定义一个私有宏。
私有宏只能从定义它们的同一个模块访问。
私有宏必须在其使用之前定义。
查看 defmacro/2
以了解更多信息,并查看 def/2
以了解有关命名和默认参数的规则。
使用给定的名称和内容定义一个模块。
此宏使用给定的 alias
作为其名称并使用给定的内容定义一个模块。它返回一个具有四个元素的元组
:module
- 模块名称
- 模块的二进制内容
- 求值内容块的结果
例子
defmodule Number do
def one, do: 1
def two, do: 2
end
#=> {:module, Number, <<70, 79, 82, ...>>, {:two, 0}}
Number.one()
#=> 1
Number.two()
#=> 2
模块名称和别名
模块名称(和别名)必须以 ASCII 大写字母开头,后面可以跟任何 ASCII 字母、数字或下划线。Elixir 的 命名约定 建议模块名称和别名以 CamelCase
格式编写。
您也可以使用原子作为模块名称,尽管它们只能包含 ASCII 字符。
嵌套
将一个模块嵌套在另一个模块中会影响嵌套模块的名称
defmodule Foo do
defmodule Bar do
end
end
在上面的例子中,创建了两个模块 - Foo
和 Foo.Bar
。当嵌套时,Elixir 会自动为内部模块创建一个别名,允许在定义它的同一个词法范围内访问第二个模块 Foo.Bar
作为 Bar
(Foo
模块)。只有在通过别名定义嵌套模块时才会发生这种情况。
如果将 Foo.Bar
模块移到其他地方,则需要将 Foo
模块中对 Bar
的引用更新为完全限定的名称(Foo.Bar
),或者必须使用 alias/2
在 Foo
模块中显式设置别名。
defmodule Foo.Bar do
# code
end
defmodule Foo do
alias Foo.Bar
# code here can refer to "Foo.Bar" as just "Bar"
end
动态名称
Elixir 模块名称可以动态生成。这在处理宏时非常有用。例如,可以编写
defmodule Module.concat(["Foo", "Bar"]) do
# contents ...
end
只要传递给 defmodule/2
的第一个参数的表达式求值为原子,Elixir 就会接受任何模块名称。请注意,当使用动态名称时,Elixir 不会将名称嵌套在当前模块下,也不会自动设置别名。
保留的模块名称
如果您尝试定义一个已存在的模块,您将收到一个警告,提示模块已被重新定义。
有些模块 Elixir 目前没有实现,但可能在将来实现。这些模块是保留的,定义它们会导致编译错误。
defmodule Any do
# code
end
** (CompileError) iex:1: module Any is reserved and cannot be defined
Elixir 保留以下模块名称:Elixir
、Any
、BitString
、PID
和 Reference
。
使当前模块中的给定定义可重写。
如果用户使用相同名称和元数定义新的函数或宏,则可覆盖的函数或宏将被丢弃。否则,将使用原始定义。
被覆盖的定义可能具有与原始定义不同的可见性:公有函数可以被私有函数覆盖,反之亦然。
宏不能被函数覆盖,反之亦然。
示例
defmodule DefaultMod do
defmacro __using__(_opts) do
quote do
def test(x, y) do
x + y
end
defoverridable test: 2
end
end
end
defmodule ChildMod do
use DefaultMod
def test(x, y) do
x * y + super(x, y)
end
end
如上面的示例所示,可以使用 super
来调用默认实现。
免责声明
谨慎使用
defoverridable
。如果您需要定义多个具有相同行为的模块,最好将默认实现移到调用方,并通过Code.ensure_loaded?/1
和function_exported?/3
检查是否存在回调。例如,在上面的示例中,假设有一个模块调用
test/2
函数。此模块可以这样定义:defmodule CallsTest do def receives_module_and_calls_test(module, x, y) do if Code.ensure_loaded?(module) and function_exported?(module, :test, 2) do module.test(x, y) else x + y end end end
带有行为的示例
您还可以将行为传递给 defoverridable
,它将标记行为中的所有回调为可覆盖的。
defmodule Behaviour do
@callback test(number(), number()) :: number()
end
defmodule DefaultMod do
defmacro __using__(_opts) do
quote do
@behaviour Behaviour
def test(x, y) do
x + y
end
defoverridable Behaviour
end
end
end
defmodule ChildMod do
use DefaultMod
def test(x, y) do
x * y + super(x, y)
end
end
使用给定的名称和主体定义一个私有函数。
私有函数只能从定义它们的模块内部访问。尝试从定义私有函数的模块外部访问它会导致 UndefinedFunctionError
异常。
有关更多信息,请查看 def/2
。
示例
defmodule Foo do
def bar do
sum(1, 2)
end
defp sum(a, b), do: a + b
end
Foo.bar()
#=> 3
Foo.sum(1, 2)
** (UndefinedFunctionError) undefined function Foo.sum/2
定义一个协议。
有关更多信息,请参阅 Protocol
模块。
定义一个结构体。
结构体是一种带标签的映射,允许开发者为键提供默认值、用于多态分派的标签以及编译时断言。有关结构体的更多信息,请查看 %/2
。
每个模块只能定义一个结构体,因为结构体与模块本身绑定在一起。调用 defstruct/1
还会定义一个 __struct__/0
函数,它返回结构体本身。
示例
defmodule User do
defstruct name: nil, age: nil
end
结构体字段在编译时进行评估,这使得它们可以是动态的。在下面的示例中,10 + 11
在编译时进行评估,并且 age 字段的值存储为 21
defmodule User do
defstruct name: nil, age: 10 + 11
end
通常,fields
参数是一个关键字列表,其中字段名作为原子键,默认值作为相应的键值。defstruct/1
还支持一个原子列表作为其参数:在这种情况下,列表中的原子将用作结构体的字段名,并且它们都将默认为 nil
。
defmodule Post do
defstruct [:title, :content, :author]
end
使用 @doc
属性向结构体添加文档,就像函数一样。
defmodule Post do
@doc "A post. The content should be valid Markdown."
defstruct [:title, :content, :author]
end
派生
虽然结构体是映射,但默认情况下,结构体不实现为映射实现的任何协议。例如,尝试将协议与 User
结构体一起使用会导致错误。
john = %User{name: "John"}
MyProtocol.call(john)
** (Protocol.UndefinedError) protocol MyProtocol not implemented for %User{...}
然而,defstruct/1
允许对协议实现进行派生。这可以通过在调用 defstruct/1
之前定义一个 @derive
属性作为列表来完成。
defmodule User do
@derive MyProtocol
defstruct name: nil, age: nil
end
MyProtocol.call(john) # it works!
一个常见的例子是 @derive
Inspect
协议,以便在打印结构体时隐藏某些字段。
defmodule User do
@derive {Inspect, only: :name}
defstruct name: nil, age: nil
end
对于 @derive
中的每个协议,Elixir 将断言该协议已为 Any
实现。如果 Any
实现定义了 __deriving__/3
回调,则将调用该回调,它应该定义实现模块。否则,将自动派生一个简单地指向 Any
实现的实现。有关 __deriving__/3
回调的更多信息,请参阅 Protocol.derive/3
。
强制键
在构建结构体时,Elixir 将自动保证所有键都属于结构体。
%User{name: "john", unknown: :key}
** (KeyError) key :unknown not found in: %User{age: 21, name: nil}
Elixir 还允许开发者强制在构建结构体时始终给出某些键。
defmodule User do
@enforce_keys [:name]
defstruct name: nil, age: 10 + 11
end
现在,尝试在没有 name 键的情况下构建结构体将失败。
%User{age: 21}
** (ArgumentError) the following keys must also be given when building struct User: [:name]
请记住,@enforce_keys
只是一个简单的编译时保证,可以帮助开发者在构建结构体时。它不会在更新时强制执行,也不会提供任何值验证。
类型
建议为结构体定义类型。按照惯例,这种类型称为 t
。要在类型中定义结构体,使用结构体字面量语法。
defmodule User do
defstruct name: "John", age: 25
@type t :: %__MODULE__{name: String.t(), age: non_neg_integer}
end
建议仅在定义结构体的类型时使用结构体语法。在引用其他结构体时,最好使用 User.t()
而不是 %User{}
。
未包含在 %User{}
中的结构体字段的类型默认为 term()
(请参阅 term/0
)。
内部结构对本地模块私有的结构体(不应该允许对它们进行模式匹配或直接访问它们的字段)应该使用 @opaque
属性。内部结构对公有的结构体应该使用 @type
。
解构两个列表,将右列表中的每个项分配给左列表中的匹配项。
与通过 =
进行模式匹配不同,如果左右列表的大小不匹配,解构将简单地停止,而不是引发错误。
示例
iex> destructure([x, y, z], [1, 2, 3, 4, 5])
iex> {x, y, z}
{1, 2, 3}
在上面的示例中,即使右侧列表的条目比左侧列表更多,解构也能正常工作。如果右侧列表较小,则剩余的元素将简单地设置为 nil
iex> destructure([x, y, z], [1])
iex> {x, y, z}
{1, nil, nil}
左侧支持您在匹配左侧使用的任何表达式。
x = 1
destructure([^x, y, z], [1, 2, 3])
上面的示例仅在 x
与右侧列表中的第一个值匹配时才有效。否则,它将引发 MatchError
(就像 =
运算符一样)。
使用给定的原因停止调用进程的执行。
由于评估此函数会导致进程终止,因此它没有返回值。
由编译器内联。
示例
当进程结束时,默认情况下它以 :normal
的原因退出。如果您想终止进程而不发出任何失败信号,也可以显式调用 exit/1
exit(:normal)
如果出现错误,您也可以使用 exit/1
以及其他原因。
exit(:seems_bad)
如果退出原因不是 :normal
,则与退出进程链接的所有进程都将崩溃(除非它们正在捕获退出)。
OTP 退出
OTP 使用退出来确定进程是否异常退出。以下退出被视为“正常”。
exit(:normal)
exit(:shutdown)
exit({:shutdown, term})
以任何其他原因退出都被视为异常并被视为崩溃。这意味着默认的监管程序行为将启动,错误报告将发出,等等。
这种行为在许多不同的地方都有依赖。例如,ExUnit
在退出测试进程时使用 exit(:shutdown)
来向链接的进程、监管树等发出信号,以便它们也礼貌地关闭。
CLI 退出
基于上述退出信号,如果由命令行启动的进程以上述三个原因中的任何一个退出,则它的退出被视为正常,并且操作系统进程将以状态 0 退出。
然而,可以通过调用以下方法自定义操作系统退出信号。
exit({:shutdown, integer})
这将导致操作系统进程以 integer
给出的状态退出,同时向所有链接的 Erlang 进程发出信号,要求它们礼貌地关闭。
任何其他退出原因都将导致操作系统进程以状态 1
退出,并且链接的 Erlang 进程将崩溃。
如果 module
已加载,并且包含具有给定 arity
的公有 function
,则返回 true
,否则返回 false
。
请注意,如果未加载该模块,此函数不会加载它。有关更多信息,请查看 Code.ensure_loaded/1
。
由编译器内联。
示例
iex> function_exported?(Enum, :map, 2)
true
iex> function_exported?(Enum, :map, 10)
false
iex> function_exported?(List, :to_string, 1)
true
通过给定的 path
获取值并更新嵌套数据结构。
这类似于 get_and_update_in/3
,只是路径是通过宏提取的,而不是传递一个列表。例如
get_and_update_in(opts[:foo][:bar], &{&1, &1 + 1})
等同于
get_and_update_in(opts, [:foo, :bar], &{&1, &1 + 1})
这也适用于嵌套结构体和指定路径的 struct.path.to.value
方式。
get_and_update_in(struct.foo.bar, &{&1, &1 + 1})
请注意,为了使此宏起作用,完整的路径必须始终对该宏可见。请参阅下面的“路径”部分。
示例
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> get_and_update_in(users["john"].age, &{&1, &1 + 1})
{27, %{"john" => %{age: 28}, "meg" => %{age: 23}}}
路径
路径可以以变量、本地或远程调用开始,后面必须跟一个或多个
foo[bar]
- 访问foo
中的键bar
;如果foo
为 nil,则返回nil
foo.bar
- 访问 map/struct 字段;如果字段不存在,则会抛出错误
以下是一些有效的路径
users["john"][:age]
users["john"].age
User.all()["john"].age
all_users()["john"].age
以下是一些无效的路径
# Does a remote call after the initial value
users["john"].do_something(arg1, arg2)
# Does not access any key or field
users
@spec get_and_update_in( structure, keys, (term() | nil -> {current_value, new_value} | :pop) ) :: {current_value, new_structure :: structure} when structure: Access.t(), keys: [any(), ...], current_value: Access.value(), new_value: Access.value()
获取嵌套结构中的值并更新它。
data
是一个嵌套结构(即实现Access
行为的 map、关键字列表或 struct)。
fun
参数接收key
的值(如果key
不存在,则为nil
),并且必须返回以下值之一
一个包含两个元素的元组
{current_value, new_value}
。在这种情况下,current_value
是检索到的值,它可以在返回之前进行操作。new_value
是要存储在key
下的新值。:pop
,这意味着应从结构中删除key
下的当前值并将其返回。
此函数使用Access
模块根据给定的keys
遍历结构,除非key
是一个函数,这将在后面的部分详细介绍。
例子
当需要检索当前值(或根据当前值计算的值)并同时更新它时,此函数很有用。例如,它可以用来读取用户的当前年龄,同时在一个步骤中将其增加 1
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> get_and_update_in(users, ["john", :age], &{&1, &1 + 1})
{27, %{"john" => %{age: 28}, "meg" => %{age: 23}}}
请注意,传递给匿名函数的当前值可能是nil
。如果任何中间值为 nil,它将抛出
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> get_and_update_in(users, ["jane", :age], &{&1, &1 + 1})
** (ArgumentError) could not put/update key :age on a nil value
函数作为键
如果键是函数,则将调用该函数,并传递三个参数
- 操作(
:get_and_update
) - 要访问的数据
- 要调用的下一个函数
这意味着get_and_update_in/3
可以扩展为提供自定义查找。缺点是函数不能作为键存储在访问的数据结构中。
当其中一个键是函数时,将调用该函数。在下面的示例中,我们使用一个函数来获取和增加列表中的所有年龄
iex> users = [%{name: "john", age: 27}, %{name: "meg", age: 23}]
iex> all = fn :get_and_update, data, next ->
...> data |> Enum.map(next) |> Enum.unzip()
...> end
iex> get_and_update_in(users, [all, :age], &{&1, &1 + 1})
{[27, 23], [%{name: "john", age: 28}, %{name: "meg", age: 24}]}
如果调用函数之前的先前值为nil
,则函数将接收nil
作为值,并且必须相应地处理它(无论是通过失败还是提供一个合理的默认值)。
Access
模块提供了许多便捷的访问器函数,例如上面定义的all
匿名函数。请参见Access.all/0
、Access.key/2
等示例。
从嵌套结构中获取值。
使用Access
模块根据给定的keys
遍历结构,除非key
是一个函数,这将在后面的部分详细介绍。
请注意,如果给定的键中没有一个是函数,则很少有理由使用get_in
而不是使用[]
编写“常规” Elixir 代码。
例子
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> get_in(users, ["john", :age])
27
iex> # Equivalent to:
iex> users["john"][:age]
27
get_in/2
还可以使用Access
模块中的访问器来遍历更复杂的数据结构。例如,这里我们使用Access.all/0
来遍历列表
iex> users = [%{name: "john", age: 27}, %{name: "meg", age: 23}]
iex> get_in(users, [Access.all(), :age])
[27, 23]
如果任何组件返回nil
,则将返回nil
,并且get_in/2
将不再进行遍历
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> get_in(users, ["unknown", :age])
nil
iex> # Equivalent to:
iex> users["unknown"][:age]
nil
iex> users = nil
iex> get_in(users, [Access.all(), :age])
nil
或者,如果您需要访问复杂的数据结构,可以使用模式匹配
case users do
%{"john" => %{age: age}} -> age
_ -> default_value
end
函数作为键
如果传递给get_in/2
的键是函数,则将调用该函数,并传递三个参数
- 操作(
:get
) - 要访问的数据
- 要调用的下一个函数
这意味着get_in/2
可以扩展为提供自定义查找。这正是上一节中的Access.all/0
键的行为方式。例如,我们可以手动实现这样的遍历,如下所示
iex> users = [%{name: "john", age: 27}, %{name: "meg", age: 23}]
iex> all = fn :get, data, next -> Enum.map(data, next) end
iex> get_in(users, [all, :age])
[27, 23]
Access
模块提供了许多便捷的访问器函数。请参见Access.all/0
、Access.key/2
等示例。
使用 structs
默认情况下,structs 不实现此函数所需的Access
行为。因此,您无法执行此操作
get_in(some_struct, [:some_key, :nested_key])
好消息是 structs 具有预定义的形状。因此,您可以改为编写
some_struct.some_key.nested_key
如果some_key
可能返回 nil,您可以始终回退到模式匹配以提供嵌套的 struct 处理
case some_struct do
%{some_key: %{nested_key: value}} -> value
%{} -> nil
end
提供一个 if/2
宏。
此宏期望第一个参数为条件,第二个参数为关键字列表。
单行示例
if(foo, do: bar)
在上面的示例中,如果foo
评估为真值(既不是false
也不是nil
),则将返回bar
。否则,将返回nil
。
可以给出else
选项来指定相反的情况
if(foo, do: bar, else: baz)
块示例
也可以将块传递给if/2
宏。上面的第一个示例将被转换为
if foo do
bar
end
请注意,do
-end
成为分隔符。第二个示例将被转换为
if foo do
bar
else
baz
end
为了比较两个以上的分句,必须使用cond/1
宏。
根据 Inspect
协议检查给定的参数。第二个参数是包含控制检查选项的关键字列表。
选项
inspect/2
接受一个选项列表,这些选项在内部被转换为Inspect.Opts
结构。请查看Inspect.Opts
的文档以查看支持的选项。
例子
iex> inspect(:foo)
":foo"
iex> inspect([1, 2, 3, 4, 5], limit: 3)
"[1, 2, 3, ...]"
iex> inspect([1, 2, 3], pretty: true, width: 0)
"[1,\n 2,\n 3]"
iex> inspect("olá" <> <<0>>)
"<<111, 108, 195, 161, 0>>"
iex> inspect("olá" <> <<0>>, binaries: :as_strings)
"\"olá\\0\""
iex> inspect("olá", binaries: :as_binaries)
"<<111, 108, 195, 161>>"
iex> inspect(~c"bar")
"~c\"bar\""
iex> inspect([0 | ~c"bar"])
"[0, 98, 97, 114]"
iex> inspect(100, base: :octal)
"0o144"
iex> inspect(100, base: :hex)
"0x64"
请注意,Inspect
协议不一定返回 Elixir 项的有效表示。在这种情况下,检查结果必须以#
开头。例如,检查函数将返回
inspect(fn a, b -> a + b end)
#=> #Function<...>
Inspect
协议可以被派生以从 structs 中隐藏某些字段,因此它们不会出现在日志、检查和类似内容中。有关更多信息,请参见Inspect
协议文档中的“派生”部分。
如果 module
已加载并包含具有给定 arity
的公共 macro
,则返回 true
,否则返回 false
。
请注意,如果未加载该模块,此函数不会加载它。有关更多信息,请查看 Code.ensure_loaded/1
。
如果module
是一个 Erlang 模块(而不是 Elixir 模块),则此函数始终返回false
。
例子
iex> macro_exported?(Kernel, :use, 2)
true
iex> macro_exported?(:erlang, :abs, 1)
false
@spec make_ref() :: reference()
返回一个几乎唯一的引用。
返回的引用将在大约 2^82 次调用后重新出现;因此它对于实际用途来说足够独特。
由编译器内联。
例子
make_ref()
#=> #Reference<0.0.0.135>
一个方便的宏,用于检查右侧(表达式)是否与左侧(模式)匹配。
例子
iex> match?(1, 1)
true
iex> match?({1, _}, {1, 2})
true
iex> map = %{a: 1, b: 2}
iex> match?(%{a: _}, map)
true
iex> a = 1
iex> match?(^a, 1)
true
match?/2
在过滤或查找可枚举中的值时非常有用
iex> list = [a: 1, b: 2, a: 3]
iex> Enum.filter(list, &match?({:a, _}, &1))
[a: 1, a: 3]
匹配中也可以给出保护子句
iex> list = [a: 1, b: 2, a: 3]
iex> Enum.filter(list, &match?({:a, x} when x < 2, &1))
[a: 1]
在函数调用之外,匹配中分配的变量将不可用(与使用=
运算符的常规模式匹配不同)
iex> match?(_x, 1)
true
iex> binding()
[]
值与模式
请记住,pin 运算符匹配的是值,而不是模式。将变量作为模式传递始终返回true
,并导致有关变量未使用的警告
# don't do this
pattern = %{a: :a}
match?(pattern, %{b: :b})
类似地,将表达式从模式中移出可能不再保留其语义。例如
match?([_ | _], [1, 2, 3])
#=> true
pattern = [_ | _]
match?(pattern, [1, 2, 3])
** (CompileError) invalid use of _. _ can only be used inside patterns to ignore values and cannot be used in expressions. Make sure you are inside a pattern or change it accordingly
另一个例子是,map 作为模式执行子集匹配,但不会在分配给变量后执行
match?(%{x: 1}, %{x: 1, y: 2})
#=> true
attrs = %{x: 1}
match?(^attrs, %{x: 1, y: 2})
#=> false
pin 运算符将使用===/2
检查值是否相等,而模式在匹配 map、列表等时有自己的规则。这种行为并非特定于match?/2
。以下代码也会抛出异常
attrs = %{x: 1}
^attrs = %{x: 1, y: 2}
#=> (MatchError) no match of right hand side value: %{x: 1, y: 2}
根据结构比较返回两个给定项中最大的项。
如果项比较相等,则返回第一个项。
这执行结构比较,其中所有 Elixir 项都可以相互比较。有关更多信息,请参阅 "结构比较" 部分 部分。
由编译器内联。
例子
iex> max(1, 2)
2
iex> max(:a, :b)
:b
根据结构比较返回两个给定项中最小的项。
如果项比较相等,则返回第一个项。
这执行结构比较,其中所有 Elixir 项都可以相互比较。有关更多信息,请参阅 "结构比较" 部分 部分。
由编译器内联。
例子
iex> min(1, 2)
1
iex> min("foo", "bar")
"bar"
通过给定的 path
从嵌套结构中弹出键。
这类似于pop_in/2
,除了路径是通过宏提取的,而不是传递列表。例如
pop_in(opts[:foo][:bar])
等同于
pop_in(opts, [:foo, :bar])
请注意,为了使此宏起作用,完整路径必须始终对该宏可见。有关支持的路径表达式的更多信息,请查看get_and_update_in/2
文档。
例子
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> pop_in(users["john"][:age])
{27, %{"john" => %{}, "meg" => %{age: 23}}}
iex> users = %{john: %{age: 27}, meg: %{age: 23}}
iex> pop_in(users.john[:age])
{27, %{john: %{}, meg: %{age: 23}}}
如果任何条目返回nil
,则将删除其键,并且删除将被视为成功。
@spec pop_in(data, [Access.get_and_update_fun(term(), data) | term(), ...]) :: {term(), data} when data: Access.container()
从给定的嵌套结构中弹出键。
使用Access
协议根据给定的keys
遍历结构,除非key
是一个函数。如果键是一个函数,它将按get_and_update_in/3
中指定的方式调用。
例子
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> pop_in(users, ["john", :age])
{27, %{"john" => %{}, "meg" => %{age: 23}}}
如果任何条目返回nil
,则将删除其键,并且删除将被视为成功。
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> pop_in(users, ["jane", :age])
{nil, %{"john" => %{age: 27}, "meg" => %{age: 23}}}
@spec put_elem(tuple(), non_neg_integer(), term()) :: tuple()
将 value
放置在 tuple
中给定的从零开始的 index
处。
由编译器内联。
例子
iex> tuple = {:foo, :bar, 3}
iex> put_elem(tuple, 0, :baz)
{:baz, :bar, 3}
通过给定的 path
将值放入嵌套结构中。
这类似于put_in/3
,除了路径是通过宏提取的,而不是传递列表。例如
put_in(opts[:foo][:bar], :baz)
等同于
put_in(opts, [:foo, :bar], :baz)
这也适用于嵌套结构体和指定路径的 struct.path.to.value
方式。
put_in(struct.foo.bar, :baz)
请注意,为了使此宏起作用,完整路径必须始终对该宏可见。有关支持的路径表达式的更多信息,请查看get_and_update_in/2
文档。
例子
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> put_in(users["john"][:age], 28)
%{"john" => %{age: 28}, "meg" => %{age: 23}}
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> put_in(users["john"].age, 28)
%{"john" => %{age: 28}, "meg" => %{age: 23}}
将值放入嵌套结构中。
使用 Access
模块根据给定的 keys
遍历结构,除非 key
是一个函数。如果 key 是一个函数,它将根据 get_and_update_in/3
中指定的进行调用。
示例
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> put_in(users, ["john", :age], 28)
%{"john" => %{age: 28}, "meg" => %{age: 23}}
如果任何中间值是 nil,它将引发
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> put_in(users, ["jane", :age], "oops")
** (ArgumentError) could not put/update key :age on a nil value
引发异常。
如果 message
是一个字符串,它将引发一个带该字符串的 RuntimeError
异常。
如果 message
是一个原子,它只使用原子作为第一个参数,[]
作为第二个参数调用 raise/2
。
如果 message
是一个异常结构体,它将按原样引发。
如果 message
是其他任何东西,raise
将引发一个 ArgumentError
异常。
示例
iex> raise "oops"
** (RuntimeError) oops
try do
1 + :foo
rescue
x in [ArithmeticError] ->
IO.puts("that was expected")
raise x
end
引发异常。
在给定参数(必须是模块名称,如 ArgumentError
或 RuntimeError
)上调用 exception/1
函数,并传递 attributes
来检索异常结构体。
任何包含对 defexception/1
宏的调用的模块都会自动实现 Exception.exception/1
回调,该回调由 raise/2
预期。有关更多信息,请参见 defexception/1
。
示例
iex> raise(ArgumentError, "Sample")
** (ArgumentError) Sample
引发异常并保留之前的堆栈跟踪。
类似于 raise/1
,但不生成新的堆栈跟踪。
注意,可以在 catch/rescue 内部使用 __STACKTRACE__
来检索当前堆栈跟踪。
示例
try do
raise "oops"
rescue
exception ->
reraise exception, __STACKTRACE__
end
引发异常并保留之前的堆栈跟踪。
reraise/3
类似于 reraise/2
,不同之处在于它将参数传递给 exception/1
函数,如 raise/2
中所述。
示例
try do
raise "oops"
rescue
exception ->
reraise WrapperError, [exception: exception], __STACKTRACE__
end
@spec send(dest :: Process.dest(), message) :: message when message: any()
将消息发送到给定的 dest
并返回该消息。
dest
可以是远程或本地 PID,本地端口,本地注册的名称,或 {registered_name, node}
格式的元组,用于表示另一个节点上的注册名称。
由编译器内联。
示例
iex> send(self(), :hello)
:hello
处理字符列表的 sigil ~C
。
它返回一个不包含插值和转义字符的字符列表,除了转义关闭的 sigil 字符本身。
字符列表是一个整数列表,其中所有整数都是有效的代码点。以下三个表达式是等价的
~C"foo\n"
[?f, ?o, ?o, ?\\, ?n]
[102, 111, 111, 92, 110]
实际上,字符列表主要用于特定场景,例如与不支持二进制作为参数的旧 Erlang 库进行交互。
示例
iex> ~C(foo)
~c"foo"
iex> ~C(f#{o}o)
~c"f\#{o}o"
iex> ~C(foo\n)
~c"foo\\n"
处理字符列表的 sigil ~c
。
它返回一个字符列表,取消转义字符并替换插值。
字符列表是一个整数列表,其中所有整数都是有效的代码点。以下三个表达式是等价的
~c"foo"
[?f, ?o, ?o]
[102, 111, 111]
实际上,字符列表主要用于特定场景,例如与不支持二进制作为参数的旧 Erlang 库进行交互。
示例
iex> ~c(foo)
~c"foo"
iex> ~c(f#{:o}o)
~c"foo"
iex> ~c(f\#{:o}o)
~c"f\#{:o}o"
如果所有代码点都在 ASCII 范围内,该列表将只作为 ~c
sigil 打印
iex> ~c"hełło"
[104, 101, 322, 322, 111]
iex> [104, 101, 108, 108, 111]
~c"hello"
有关更多信息,请参见 Inspect.Opts
。
处理日期的 sigil ~D
。
默认情况下,此 sigil 使用内置的 Calendar.ISO
,它要求日期以 ISO8601 格式编写
~D[yyyy-mm-dd]
例如
~D[2015-01-13]
如果您使用的是其他日历,只要您在表示法后面跟一个空格和日历名称,就可以使用任何表示法
~D[SOME-REPRESENTATION My.Alternative.Calendar]
小写 ~d
变体不存在,因为插值和转义字符对日期 sigil 没有用。
有关日期的更多信息,请参见 Date
模块。
示例
iex> ~D[2015-01-13]
~D[2015-01-13]
处理朴素日期时间的 sigil ~N
。
默认情况下,此 sigil 使用内置的 Calendar.ISO
,它要求朴素日期时间以 ISO8601 格式编写
~N[yyyy-mm-dd hh:mm:ss]
~N[yyyy-mm-dd hh:mm:ss.ssssss]
~N[yyyy-mm-ddThh:mm:ss.ssssss]
例如
~N[2015-01-13 13:00:07]
~N[2015-01-13T13:00:07.123]
如果您使用的是其他日历,只要您在表示法后面跟一个空格和日历名称,就可以使用任何表示法
~N[SOME-REPRESENTATION My.Alternative.Calendar]
小写 ~n
变体不存在,因为插值和转义字符对日期时间 sigil 没有用。
有关朴素日期时间的更多信息,请参见 NaiveDateTime
模块。
示例
iex> ~N[2015-01-13 13:00:07]
~N[2015-01-13 13:00:07]
iex> ~N[2015-01-13T13:00:07.001]
~N[2015-01-13 13:00:07.001]
处理正则表达式的 sigil ~r
。
它返回一个正则表达式模式,取消转义字符并替换插值。
有关正则表达式的更多信息,请参见 Regex
模块。
示例
iex> Regex.match?(~r/foo/, "foo")
true
iex> Regex.match?(~r/a#{:b}c/, "abc")
true
虽然 ~r
sigil 允许使用括号和方括号作为分隔符,但建议使用 "
或 /
来避免与保留的正则表达式字符的转义冲突。
处理字符串的 sigil ~S
。
它返回一个不包含插值和转义字符的字符串,除了转义关闭的 sigil 字符本身。
示例
iex> ~S(foo)
"foo"
iex> ~S(f#{o}o)
"f\#{o}o"
iex> ~S(\o/)
"\\o/"
但是,如果您想在字符串上重新使用 sigil 字符本身,您需要对其进行转义
iex> ~S((\))
"()"
处理字符串的 sigil ~s
。
它返回一个字符串,就像它是一个双引号字符串一样,取消转义字符并替换插值。
示例
iex> ~s(foo)
"foo"
iex> ~s(f#{:o}o)
"foo"
iex> ~s(f\#{:o}o)
"f\#{:o}o"
处理时间的 sigil ~T
。
默认情况下,此 sigil 使用内置的 Calendar.ISO
,它要求时间以 ISO8601 格式编写
~T[hh:mm:ss]
~T[hh:mm:ss.ssssss]
例如
~T[13:00:07]
~T[13:00:07.123]
如果您使用的是其他日历,只要您在表示法后面跟一个空格和日历名称,就可以使用任何表示法
~T[SOME-REPRESENTATION My.Alternative.Calendar]
小写 ~t
变体不存在,因为插值和转义字符对时间 sigil 没有用。
有关时间的更多信息,请参见 Time
模块。
示例
iex> ~T[13:00:07]
~T[13:00:07]
iex> ~T[13:00:07.001]
~T[13:00:07.001]
处理 sigil ~U
以创建 UTC DateTime
。
默认情况下,此 sigil 使用内置的 Calendar.ISO
,它要求 UTC 日期时间以 ISO8601 格式编写
~U[yyyy-mm-dd hh:mm:ssZ]
~U[yyyy-mm-dd hh:mm:ss.ssssssZ]
~U[yyyy-mm-ddThh:mm:ss.ssssss+00:00]
例如
~U[2015-01-13 13:00:07Z]
~U[2015-01-13T13:00:07.123+00:00]
如果您使用的是其他日历,只要您在表示法后面跟一个空格和日历名称,就可以使用任何表示法
~U[SOME-REPRESENTATION My.Alternative.Calendar]
给定的 datetime_string
必须包含 "Z" 或 "00:00" 偏移量,该偏移量将其标记为 UTC,否则会引发错误。
小写 ~u
变体不存在,因为插值和转义字符对日期时间 sigil 没有用。
有关日期时间的更多信息,请参见 DateTime
模块。
示例
iex> ~U[2015-01-13 13:00:07Z]
~U[2015-01-13 13:00:07Z]
iex> ~U[2015-01-13T13:00:07.001+00:00]
~U[2015-01-13 13:00:07.001Z]
处理单词列表的 sigil ~W
。
它返回一个由空格分隔的 "单词" 列表,不包含插值和转义字符,除了转义关闭的 sigil 字符本身。
修饰符
s
: 列表中的单词是字符串(默认)a
: 列表中的单词是原子c
: 列表中的单词是字符列表
示例
iex> ~W(foo #{bar} baz)
["foo", "\#{bar}", "baz"]
处理单词列表的 sigil ~w
。
它返回一个由空格分隔的 "单词" 列表。对每个单词进行字符取消转义和插值。
修饰符
s
: 列表中的单词是字符串(默认)a
: 列表中的单词是原子c
: 列表中的单词是字符列表
示例
iex> ~w(foo #{:bar} baz)
["foo", "bar", "baz"]
iex> ~w(foo #{" bar baz "})
["foo", "bar", "baz"]
iex> ~w(--source test/enum_test.exs)
["--source", "test/enum_test.exs"]
iex> ~w(foo bar baz)a
[:foo, :bar, :baz]
iex> ~w(foo bar baz)c
[~c"foo", ~c"bar", ~c"baz"]
生成给定的函数并返回其 PID。
通常,开发人员不使用 spawn
函数,而是使用诸如 Task
,GenServer
和 Agent
之类的抽象,这些抽象构建在 spawn
之上,这些抽象在进程自省和调试方面提供了更多便利。
查看 Process
模块以获取更多与进程相关的函数。
匿名函数接收 0 个参数,可以返回任何值。
由编译器内联。
示例
current = self()
child = spawn(fn -> send(current, {self(), 1 + 2}) end)
receive do
{^child, 3} -> IO.puts("Received 3 back")
end
从给定的 module
生成给定的函数 fun
,传递给定的 args
并返回其 PID。
通常,开发人员不使用 spawn
函数,而是使用诸如 Task
,GenServer
和 Agent
之类的抽象,这些抽象构建在 spawn
之上,这些抽象在进程自省和调试方面提供了更多便利。
查看 Process
模块以获取更多与进程相关的函数。
由编译器内联。
示例
spawn(SomeModule, :function, [1, 2, 3])
生成给定的函数,将其链接到当前进程,并返回其 PID。
通常,开发人员不使用 spawn
函数,而是使用诸如 Task
,GenServer
和 Agent
之类的抽象,这些抽象构建在 spawn
之上,这些抽象在进程自省和调试方面提供了更多便利。
查看 Process
模块以获取更多与进程相关的函数。有关链接的更多信息,请查看 Process.link/1
。
匿名函数接收 0 个参数,可以返回任何值。
由编译器内联。
示例
current = self()
child = spawn_link(fn -> send(current, {self(), 1 + 2}) end)
receive do
{^child, 3} -> IO.puts("Received 3 back")
end
从给定的 module
生成给定的函数 fun
,传递给定的 args
,将其链接到当前进程,并返回其 PID。
通常,开发人员不使用 spawn
函数,而是使用诸如 Task
,GenServer
和 Agent
之类的抽象,这些抽象构建在 spawn
之上,这些抽象在进程自省和调试方面提供了更多便利。
查看 Process
模块以获取更多与进程相关的函数。有关链接的更多信息,请查看 Process.link/1
。
由编译器内联。
示例
spawn_link(SomeModule, :function, [1, 2, 3])
生成给定的函数,监控它并返回其 PID 和监控引用。
通常,开发人员不使用 spawn
函数,而是使用诸如 Task
,GenServer
和 Agent
之类的抽象,这些抽象构建在 spawn
之上,这些抽象在进程自省和调试方面提供了更多便利。
查看 Process
模块以获取更多与进程相关的函数。
匿名函数接收 0 个参数,可以返回任何值。
由编译器内联。
示例
current = self()
spawn_monitor(fn -> send(current, {self(), 1 + 2}) end)
生成给定的模块和函数,传递给定的参数,监控它并返回其 PID 和监控引用。
通常,开发人员不使用 spawn
函数,而是使用诸如 Task
,GenServer
和 Agent
之类的抽象,这些抽象构建在 spawn
之上,这些抽象在进程自省和调试方面提供了更多便利。
查看 Process
模块以获取更多与进程相关的函数。
由编译器内联。
示例
spawn_monitor(SomeModule, :function, [1, 2, 3])
@spec struct(module() | struct(), Enumerable.t()) :: struct()
创建和更新结构。
参数 struct
可以是原子(定义 defstruct
)或 struct
本身。第二个参数是任何 Enumerable
,它在枚举期间发出两个元素的元组(键值对)。
在 Enumerable
中不存在于结构中的键将被自动丢弃。注意,键必须是原子,因为在定义结构时只允许使用原子。如果 Enumerable
中存在重复的键,则将使用最后一个条目(与 Map.new/1
的行为相同)。
此函数对于动态创建和更新结构以及将映射转换为结构很有用;在后一种情况下,只需将适当的 :__struct__
字段插入映射可能不够,应该使用 struct/2
。
示例
defmodule User do
defstruct name: "john"
end
struct(User)
#=> %User{name: "john"}
opts = [name: "meg"]
user = struct(User, opts)
#=> %User{name: "meg"}
struct(user, unknown: "value")
#=> %User{name: "meg"}
struct(User, %{name: "meg"})
#=> %User{name: "meg"}
# String keys are ignored
struct(User, %{"name" => "meg"})
#=> %User{name: "john"}
@spec struct!(module() | struct(), Enumerable.t()) :: struct()
类似于 struct/2
,但会检查键的有效性。
函数 struct!/2
模仿了结构的编译时行为。这意味着
在构建结构时,如
struct!(SomeStruct, key: :value)
,它等同于%SomeStruct{key: :value}
,因此此函数将检查每个给定的键值是否属于结构。如果结构通过@enforce_keys
强制执行任何键,那么这些键也会被强制执行;在更新结构时,如
struct!(%SomeStruct{}, key: :value)
,它等同于%SomeStruct{struct | key: :value}
,因此此函数将检查每个给定的键值是否属于结构。但是,更新结构不会强制执行键,因为键只在构建时强制执行;
将第一个参数 value
传递给第二个参数,一个函数 fun
,并返回 value
本身。
对于使用 |>/2
运算符在管道中运行同步副作用很有用。
示例
iex> tap(1, fn x -> x + 1 end)
1
最常见的是,这在管道中使用,使用 |>/2
运算符。例如,假设您要检查数据结构的一部分。您可以编写
%{a: 1}
|> Map.update!(:a, & &1 + 2)
|> tap(&IO.inspect(&1.a))
|> Map.update!(:a, & &1 * 2)
将第一个参数 value
传递给第二个参数,一个函数 fun
,并返回调用 fun
的结果。
换句话说,它使用 value
作为参数调用函数 fun
,并返回其结果。
这在管道中使用最常见,使用 |>/2
运算符,允许您将值传递给其第一个参数之外的函数。
示例
iex> 1 |> then(fn x -> x * 2 end)
2
iex> 1 |> then(fn x -> Enum.drop(["a", "b", "c"], x) end)
["b", "c"]
从函数中进行非局部返回。
通常不建议使用 throw/1
,因为它允许函数从其常规执行流程中退出,这会使代码更难阅读。此外,所有抛出的值都必须由 try/catch
捕获。有关更多信息,请参见 try/1
。
由编译器内联。
根据 List.Chars
协议将给定的项转换为字符列表。
示例
iex> to_charlist(:foo)
~c"foo"
根据 String.Chars
协议将参数转换为字符串。
这是在进行字符串插值时调用的函数。
示例
iex> to_string(:foo)
"foo"
提供一个 unless
宏。
此宏在 condition
计算为假值(false
或 nil
)时,将评估并返回作为第二个参数传入的 do
块。否则,如果存在,它将返回 else
块的值,否则返回 nil
。
另请参见 if/2
。
示例
iex> unless(Enum.empty?([]), do: "Hello")
nil
iex> unless(Enum.empty?([1, 2, 3]), do: "Hello")
"Hello"
iex> unless Enum.sum([2, 2]) == 5 do
...> "Math still works"
...> else
...> "Math is broken"
...> end
"Math still works"
通过给定的 path
更新嵌套结构。
这类似于 update_in/3
,只是路径是通过宏而不是传递列表提取的。例如
update_in(opts[:foo][:bar], &(&1 + 1))
等同于
update_in(opts, [:foo, :bar], &(&1 + 1))
这也适用于嵌套结构体和指定路径的 struct.path.to.value
方式。
update_in(struct.foo.bar, &(&1 + 1))
请注意,为了使此宏起作用,完整路径必须始终对该宏可见。有关支持的路径表达式的更多信息,请查看get_and_update_in/2
文档。
示例
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> update_in(users["john"][:age], &(&1 + 1))
%{"john" => %{age: 28}, "meg" => %{age: 23}}
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> update_in(users["john"].age, &(&1 + 1))
%{"john" => %{age: 28}, "meg" => %{age: 23}}
更新嵌套结构中的键。
使用 Access
模块根据给定的 keys
遍历结构,除非 key
是一个函数。如果 key 是一个函数,它将根据 get_and_update_in/3
中指定的进行调用。
data
是一个嵌套结构(即,实现了 Access
行为的映射、关键字列表或结构)。fun
参数接收 key
的值(如果 key
不存在则为 nil
),结果将替换结构中的值。
示例
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> update_in(users, ["john", :age], &(&1 + 1))
%{"john" => %{age: 28}, "meg" => %{age: 23}}
请注意,传递给匿名函数的当前值可能是nil
。如果任何中间值为 nil,它将抛出
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> update_in(users, ["jane", :age], & &1 + 1)
** (ArgumentError) could not put/update key :age on a nil value
在当前上下文中使用给定的模块。
当调用
use MyModule, some: :options
Elixir 将调用 MyModule.__using__/1
,并将 use
的第二个参数作为其参数传递。由于 __using__/1
通常是一个宏,因此所有常见的宏规则都适用,其返回值应该是引用的代码,然后将其插入调用 use/2
的位置。
代码注入
use MyModule
在调用方充当**代码注入点**。鉴于调用方use MyModule
对代码注入方式的控制有限,因此应该谨慎使用use/2
。如果可以,请尽可能避免使用,而是使用import/2
或alias/2
。
示例
例如,要使用 Elixir 附带的 ExUnit
框架编写测试用例,开发人员应该使用 ExUnit.Case
模块
defmodule AssertionTest do
use ExUnit.Case, async: true
test "always pass" do
assert true
end
end
在此示例中,Elixir 将使用关键字列表 [async: true]
作为其参数调用 ExUnit.Case
模块中的 __using__/1
宏。
换句话说,use/2
转换为
defmodule AssertionTest do
require ExUnit.Case
ExUnit.Case.__using__(async: true)
test "always pass" do
assert true
end
end
其中 ExUnit.Case
定义了 __using__/1
宏
defmodule ExUnit.Case do
defmacro __using__(opts) do
# do something with opts
quote do
# return some code to inject in the caller
end
end
end
最佳实践
__using__/1
通常用于需要在调用方中设置一些状态(通过模块属性)或回调(如 @before_compile
,有关更多信息,请参见 Module
的文档)。
__using__/1
也可用于从不同模块中别名、需要或导入功能
defmodule MyModule do
defmacro __using__(_opts) do
quote do
import MyModule.Foo
import MyModule.Bar
import MyModule.Baz
alias MyModule.Repo
end
end
end
但是,如果 __using__/1
所做的只是导入、别名或需要模块本身,请不要提供它。例如,避免这种情况
defmodule MyModule do
defmacro __using__(_opts) do
quote do
import MyModule
end
end
end
在这种情况下,开发人员应该直接导入或别名模块,以便他们可以根据需要自定义它们,而无需 use/2
背后的间接操作。开发人员还必须避免在 __using__/1
中定义函数。
鉴于 use MyModule
可以生成任何代码,因此开发人员可能难以理解 use MyModule
的影响。
为此,为了提供指导和清晰度,我们建议开发人员在其 @moduledoc
中包含一个警示块,解释 use MyModule
如何影响其代码。例如,GenServer
文档概述
use GenServer
当您使用
use GenServer
时,GenServer
模块将设置@behaviour GenServer
并定义child_spec/1
函数,因此您的模块可用作监督树中的子项。
这提供了使用模块如何影响用户代码的快速摘要。请记住,只列出对模块公共 API 所做的更改。例如,如果 use MyModule
设置了一个名为 @_my_module_info
的内部属性,而该属性永远不打算公开,则不应列出它。
为了方便起见,生成上面警示块的标记符号为
> #### `use GenServer` {: .info}
>
> When you `use GenServer`, the GenServer module will
> set `@behaviour GenServer` and define a `child_spec/1`
> function, so your module can be used as a child
> in a supervision tree.
标记给定的变量不应被卫生化。
此宏需要一个变量,它通常在 quote/2
中调用,以标记不应该对变量进行卫生处理。有关更多信息,请参见 quote/2
。
示例
iex> Kernel.var!(example) = 1
1
iex> Kernel.var!(example)
1
管道运算符。
此运算符将左侧的表达式作为第一个参数引入到右侧的函数调用中。
示例
iex> [1, [2], 3] |> List.flatten()
[1, 2, 3]
上面的示例与调用 List.flatten([1, [2], 3])
相同。
|>/2
运算符在希望执行一系列类似管道的操作时最为有用
iex> [1, [2], 3] |> List.flatten() |> Enum.map(fn x -> x * 2 end)
[2, 4, 6]
在上面的示例中,列表 [1, [2], 3]
被作为第一个参数传递给 List.flatten/1
函数,然后扁平化的列表被作为第一个参数传递给 Enum.map/2
函数,该函数将列表的每个元素翻倍。
换句话说,上面的表达式简化为
Enum.map(List.flatten([1, [2], 3]), fn x -> x * 2 end)
陷阱
使用管道运算符时,有两个常见的陷阱。
第一个与运算符优先级有关。例如,以下表达式
String.graphemes "Hello" |> Enum.reverse
转换为
String.graphemes("Hello" |> Enum.reverse())
这会导致错误,因为 Enumerable
协议未为二进制文件定义。添加显式括号可以解决歧义
String.graphemes("Hello") |> Enum.reverse()
或者,更好的是
"Hello" |> String.graphemes() |> Enum.reverse()
第二个限制是 Elixir 总是将管道传递给函数调用。因此,要将管道传递给匿名函数,您需要调用它
some_fun = &Regex.replace(~r/l/, &1, "L")
"Hello" |> some_fun.()
或者,您可以使用 then/2
来实现相同的效果
some_fun = &Regex.replace(~r/l/, &1, "L")
"Hello" |> then(some_fun)
当您想要将管道传递给一个函数,但该函数的值预期在第一个参数之外时,then/2
最常使用,例如上面的示例。通过用其值替换 some_fun
,我们得到
"Hello" |> then(&Regex.replace(~r/l/, &1, "L"))
布尔“或”运算符。
提供一个短路运算符,仅当第一个表达式不计算为真值(即,它是 nil
或 false
)时才计算并返回第二个表达式。否则返回第一个表达式。
在保护子句中不允许。
示例
iex> Enum.empty?([1]) || Enum.empty?([1])
false
iex> List.first([]) || true
true
iex> Enum.empty?([1]) || 1
1
iex> Enum.empty?([]) || throw(:bad)
true
请注意,与 or/2
不同,此运算符接受任何表达式作为第一个参数,而不仅仅是布尔值。