查看源代码 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 - 具有名称的字面常量(truefalsenil 是原子)
  • 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 - 版本和要求的表示形式

系统模块

与底层系统交互的模块,例如

  • IO - 处理输入和输出
  • File - 与底层文件系统交互
  • Path - 操作文件系统路径
  • System - 读取和写入系统信息

协议

协议将多态调度添加到 Elixir。它们是数据类型可以实现的契约。有关协议的更多信息,请参阅 Protocol。Elixir 在标准库中提供以下协议

  • Collectable - 将数据收集到数据类型中
  • Enumerable - 处理 Elixir 中的集合。Enum 模块提供了用于处理集合的急切函数,Stream 模块提供了惰性函数
  • Inspect - 将数据类型转换为其编程语言表示形式
  • List.Chars - 将数据类型转换为其外部世界表示形式,作为字符列表(非编程 based)
  • String.Chars - 将数据类型转换为其外部世界表示形式,作为字符串(非编程 based)

基于进程和以应用程序为中心的函数

以下模块构建在进程之上,以提供并发性、容错性和更多功能。

  • Agent - 封装可变状态的进程
  • Application - 用于启动、停止和配置应用程序的函数
  • GenServer - 通用客户端-服务器 API
  • Registry - 基于进程的键值存储
  • Supervisor - 负责启动、监督和关闭其他进程的进程
  • Task - 执行计算的进程
  • Task.Supervisor - 用于专门管理任务的监管者

支持文档

在侧边栏的“页面”部分,您将找到教程、指南和参考文档,这些文档更详细地概述了 Elixir 语义和行为。它们是

守卫

此模块包含 Elixir 开发人员使用的内置守卫。它们是一组预定义的函数和宏,用于增强模式匹配,通常在 when 运算符之后调用。例如

def drive(%User{age: age}) when age >= 16 do
  ...
end

上面的子句只有在用户的年龄大于或等于 16 时才会被调用。守卫还支持使用 andor 连接多个条件。如果所有守卫表达式都计算为 true,则整个守卫为真。在 模式和守卫 页面中提供了对守卫的更完整介绍。

真值和假值

除了布尔值 truefalse 之外,Elixir 还具有“真值”或“假值”的概念。

  • 当值既不是 false 也不是 nil 时,它是真值
  • 当值是 falsenil 时,它是假值

Elixir 有像 and/2 这样的函数,它们 *仅* 与布尔值一起工作,但也有像 &&/2!/1 这样的函数,它们与这些真值/假值一起工作。

结构比较

此模块中的函数执行结构比较。这允许使用比较运算符比较不同的数据类型

iex> 1 < :an_atom
true

这是可能的,因此 Elixir 开发人员可以创建集合(如字典和有序集),这些集合在其中存储混合的数据类型。为了理解这为什么很重要,让我们讨论一下我们在软件中遇到的两种类型的比较:*结构* 和 *语义*。

结构意味着我们正在比较底层数据结构,并且我们通常希望这些操作尽可能快,因为它用于为语言中的几种算法和数据结构提供动力。语义比较关心每种数据类型代表什么。例如,从语义上讲,比较 TimeDate 是没有意义的。

一个显示结构比较和语义比较之间差异的例子是字符串:“alien” 排在“office” 之前 ("alien" < "office") 但“álien” 大于“office”。这是因为 < 比较形成字符串的底层字节。如果您要进行字母顺序排序,您可能希望“álien” 也出现在“office” 之前。

这意味着 **Elixir 中的比较是结构性的**,因为它旨在尽可能高效地比较数据类型,以创建灵活且高效的数据结构。这种区别对于提供排序的函数特别重要,例如 >/2</2>=/2<=/2min/2max/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

如果 keymap 中的键,则返回 true;否则返回 false

如果 termnil,则返回 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

firstlast 创建一个范围。

firstlast 创建一个步长为 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
@spec +integer() :: integer()
@spec +float() :: float()

算术正号一元运算符。

允许在守卫测试中使用。由编译器内联。

示例

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
@spec number() / number() :: float()

算术除法运算符。

结果始终是浮点数。如果您想要整数除法或余数,请使用 div/2rem/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
@spec term() != term() :: boolean()

不等于运算符。

如果两个项不相等,则返回 true

此运算符认为 1 和 1.0 相等。对于匹配比较,请改用 !==/2

这执行结构比较,其中所有 Elixir 项都可以相互比较。有关更多信息,请参阅 "结构比较" 部分 部分。

允许在守卫测试中使用。由编译器内联。

示例

iex> 1 != 2
true

iex> 1 != 1.0
false
@spec term() !== term() :: boolean()

严格不等于运算符。

如果两个项完全不相等,则返回 true。请参阅 ===/2 以了解“完全相等”的定义。

这执行结构比较,其中所有 Elixir 项都可以相互比较。有关更多信息,请参阅 "结构比较" 部分 部分。

允许在守卫测试中使用。由编译器内联。

示例

iex> 1 !== 2
true

iex> 1 !== 1.0
true
@spec term() < term() :: boolean()

小于运算符。

如果 left 小于 right,则返回 true

这执行结构比较,其中所有 Elixir 项都可以相互比较。有关更多信息,请参阅 "结构比较" 部分 部分。

允许在守卫测试中使用。由编译器内联。

示例

iex> 1 < 2
true
@spec term() <= term() :: boolean()

小于等于运算符。

如果 left 小于或等于 right,则返回 true

这执行结构比较,其中所有 Elixir 项都可以相互比较。有关更多信息,请参阅 "结构比较" 部分 部分。

允许在守卫测试中使用。由编译器内联。

示例

iex> 1 <= 2
true
@spec term() == term() :: boolean()

等于运算符。如果两个项相等,则返回 true

此运算符认为 1 和 1.0 相等。对于更严格的语义,请改用 ===/2

这执行结构比较,其中所有 Elixir 项都可以相互比较。有关更多信息,请参阅 "结构比较" 部分 部分。

允许在守卫测试中使用。由编译器内联。

示例

iex> 1 == 2
false

iex> 1 == 1.0
true
@spec term() === term() :: boolean()

严格等于运算符。

如果两个项完全相等,则返回 true

只有当两个项的值相同且类型相同,才认为它们完全相等。例如,1 == 1.0 返回 true,但由于它们类型不同,1 === 1.0 返回 false

这执行结构比较,其中所有 Elixir 项都可以相互比较。有关更多信息,请参阅 "结构比较" 部分 部分。

允许在守卫测试中使用。由编译器内联。

示例

iex> 1 === 2
false

iex> 1 === 1.0
false
@spec term() > term() :: boolean()

大于运算符。

如果 left 大于 right,则返回 true

这执行结构比较,其中所有 Elixir 项都可以相互比较。有关更多信息,请参阅 "结构比较" 部分 部分。

允许在守卫测试中使用。由编译器内联。

示例

iex> 1 > 2
false
@spec term() >= term() :: boolean()

大于等于运算符。

如果 left 大于或等于 right,则返回 true

这执行结构比较,其中所有 Elixir 项都可以相互比较。有关更多信息,请参阅 "结构比较" 部分 部分。

允许在守卫测试中使用。由编译器内联。

示例

iex> 1 >= 2
false
@spec abs(number()) :: number()

返回一个整数或浮点数,它是 number 的算术绝对值。

允许在守卫测试中使用。由编译器内联。

示例

iex> abs(-3.33)
3.33

iex> abs(-3)
3

严格布尔“与”运算符。

如果 leftfalse,则返回 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!"
链接到此函数

binary_part(binary, start, length)

查看源代码
@spec binary_part(binary(), non_neg_integer(), integer()) :: binary()

提取二进制文件在 start 处大小为 size 的部分。

如果 startsize 以任何方式引用二进制数据外部,则会引发 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
链接到此函数

ceil(number)

查看源代码 (自 1.8.0 起)
@spec ceil(number()) :: integer()

返回大于或等于 number 的最小整数。

如果您想对其他小数位进行向上取整操作,请改用 Float.ceil/2

允许在守卫测试中使用。由编译器内联。

示例

iex> ceil(10)
10

iex> ceil(10.1)
11

iex> ceil(-10.1)
-10
链接到此函数

div(dividend, divisor)

查看源代码
@spec div(integer(), neg_integer() | pos_integer()) :: integer()

执行整数除法。

如果其中一个参数不是整数,或者 divisor0,则会引发 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
链接到此函数

floor(number)

查看源代码 (自 1.8.0 起)
@spec floor(number()) :: integer()

返回小于或等于 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/2Code.format_string!/2 将把所有此 AST 的出现转换为 left not in right

@spec is_atom(term()) :: boolean()

如果 term 是一个原子,则返回 true;否则返回 false

允许在守卫测试中使用。由编译器内联。

示例

iex> is_atom(false)
true

iex> is_atom(:name)
true

iex> is_atom(AnAtom)
true

iex> is_atom("true")
false
@spec is_binary(term()) :: boolean()

如果 term 是一个二进制文件,则返回 true;否则返回 false

二进制总是包含完整数量的字节。

允许在守卫测试中使用。由编译器内联。

示例

iex> is_binary("foo")
true
iex> is_binary(<<1::3>>)
false
@spec is_bitstring(term()) :: boolean()

如果 term 是一个位串(包括二进制文件),则返回 true;否则返回 false

允许在守卫测试中使用。由编译器内联。

示例

iex> is_bitstring("foo")
true
iex> is_bitstring(<<1::3>>)
true
@spec is_boolean(term()) :: boolean()

如果 term 是原子 true 或原子 false(即布尔值),则返回 true;否则返回 false

允许在守卫测试中使用。由编译器内联。

示例

iex> is_boolean(false)
true

iex> is_boolean(true)
true

iex> is_boolean(:test)
false
链接到此宏

is_exception(term)

查看源代码 (自 1.11.0 起) (宏)

如果 term 是一个异常,则返回 true;否则返回 false

允许在守卫测试中使用。

示例

iex> is_exception(%RuntimeError{})
true

iex> is_exception(%{})
false
链接到此宏

is_exception(term, name)

查看源代码 (自 1.11.0 起) (宏)

如果 term 是一个名为 name 的异常,则返回 true;否则返回 false

允许在守卫测试中使用。

示例

iex> is_exception(%RuntimeError{}, RuntimeError)
true

iex> is_exception(%RuntimeError{}, Macro.Env)
false
@spec is_float(term()) :: boolean()

如果 term 是一个浮点数,则返回 true;否则返回 false

允许在守卫测试中使用。由编译器内联。

@spec is_function(term()) :: boolean()

如果 term 是一个函数,则返回 true;否则返回 false

允许在守卫测试中使用。由编译器内联。

示例

iex> is_function(fn x -> x + x end)
true

iex> is_function("not a function")
false
链接到此函数

is_function(term, arity)

查看源代码
@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
@spec is_integer(term()) :: boolean()

如果 term 是一个整数,则返回 true;否则返回 false

允许在守卫测试中使用。由编译器内联。

@spec is_list(term()) :: boolean()

如果 term 是一个包含零个或多个元素的列表,则返回 true;否则返回 false

允许在守卫测试中使用。由编译器内联。

@spec is_map(term()) :: boolean()

如果 term 是一个映射,则返回 true;否则返回 false

允许在守卫测试中使用。由编译器内联。

链接到此函数

is_map_key(map, key)

查看源代码 (自 1.10.0 起)
@spec is_map_key(map(), term()) :: boolean()

如果 keymap 中的键,则返回 true;否则返回 false

如果第一个元素不是映射,则会引发 BadMapError

允许在守卫测试中使用。由编译器内联。

示例

iex> is_map_key(%{a: "foo", b: "bar"}, :a)
true

iex> is_map_key(%{a: "foo", b: "bar"}, :c)
false

如果 termnil,则返回 true,否则返回 false

在保护子句中允许。

示例

iex> is_nil(1)
false

iex> is_nil(nil)
true
@spec is_number(term()) :: boolean()

如果 term 是一个整数或一个浮点数,则返回 true;否则返回 false

允许在守卫测试中使用。由编译器内联。

@spec is_pid(term()) :: boolean()

如果 term 是一个 PID(进程标识符),则返回 true;否则返回 false

允许在守卫测试中使用。由编译器内联。

@spec is_port(term()) :: boolean()

如果 term 是一个端口标识符,则返回 true;否则返回 false

允许在守卫测试中使用。由编译器内联。

@spec is_reference(term()) :: boolean()

如果 term 是一个引用,则返回 true;否则返回 false

允许在守卫测试中使用。由编译器内联。

链接到此宏

is_struct(term)

查看源代码 (自 1.10.0 起) (宏)

如果 term 是一个结构体,则返回 true;否则返回 false

允许在守卫测试中使用。

示例

iex> is_struct(URI.parse("/"))
true

iex> is_struct(%{})
false
链接到此宏

is_struct(term, name)

查看源代码 (自 1.11.0 起) (宏)

如果 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
@spec is_tuple(term()) :: boolean()

如果 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

允许在守卫测试中使用。由编译器内联。

@spec node(pid() | reference() | port()) :: node()

返回给定参数所在的节点。参数可以是 PID、引用或端口。如果本地节点不可用,则返回 :nonode@nohost

允许在守卫测试中使用。由编译器内联。

@spec not true :: false
@spec not false :: true

严格布尔“非”运算符。

value 必须是布尔值;如果不是,将引发 ArgumentError 异常。

允许在守卫测试中使用。由编译器内联。

示例

iex> not false
true

严格布尔“或”运算符。

如果 lefttrue,则返回 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
链接到此函数

rem(dividend, divisor)

查看源代码
@spec rem(integer(), neg_integer() | pos_integer()) :: integer()

计算整数除法的余数。

rem/2 使用截断除法,这意味着结果将始终具有 dividend 的符号。

如果其中一个参数不是整数,或者 divisor0,则会引发 ArithmeticError 异常。

允许在守卫测试中使用。由编译器内联。

示例

iex> rem(5, 2)
1
iex> rem(6, -4)
2
@spec round(number()) :: integer()

将数字四舍五入到最接近的整数。

如果该数字与两个最接近的整数等距,则舍入到零。

允许在守卫测试中使用。由编译器内联。

示例

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
@spec trunc(number()) :: integer()

返回 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 不同,此运算符接受任何表达式作为第一个参数,而不仅仅是布尔值。

链接到此函数

base ** exponent

查看源代码 (自 1.13.0 起)
@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]
@spec list() -- list() :: list()

列表减法运算符。对右列表中的每个元素,删除左列表中第一个出现的元素。

此函数经过优化,因此 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]
链接到此宏

..

查看源代码 (自 1.14.0 起) (宏)

创建完整切片范围 0..-1//1

此宏返回具有以下属性的范围

  • 枚举时,它是空的

  • 当用作 slice 时,它将按原样返回切片元素

有关更多信息,请参见 ..///3 以及 Range 模块。

示例

iex> Enum.to_list(..)
[]

iex> String.slice("Hello world!", ..)
"Hello world!"

firstlast 创建一个范围。

如果 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

查看源代码 (自 1.12.0 起) (宏)

firstlast 创建一个步长为 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)
[]

布尔“非”运算符。

接收任何值(不仅仅是布尔值),如果 valuefalsenil,则返回 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 异常。

@spec String.t() =~ (String.t() | Regex.t()) :: boolean()

基于文本的匹配运算符。将 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

上面的代码将对模块 FooBarBaz 定义编译时依赖项,这样,如果它们中的任何一个发生更改,当前模块就必须重新编译。在这种情况下,最好完全避免使用模块属性。

def handle_arg(arg) when arg in [Foo, Bar, Baz] do
  ...
end

在引号内使用时,表示给定的别名不应被卫生化。这意味着在宏扩展时将扩展该别名。

有关更多信息,请查看 quote/2

@spec apply((... -> any()), [any()]) :: any()

使用参数列表 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
链接到此函数

apply(module, function_name, args)

查看源代码
@spec apply(module(), function_name :: atom(), [any()]) :: any()

使用参数列表 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]
链接到此函数

binary_slice(binary, range)

查看源代码 (自 1.14.0 起)

从范围开始处的偏移量返回一个二进制文件,到范围结束处的偏移量。

如果范围的开始或结束为负数,则它们将根据二进制文件的大小转换为正索引。例如,-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)
""
链接到此函数

binary_slice(binary, start, size)

查看源代码 (自 1.14.0 起)

返回从偏移量 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)
""
链接到此宏

binding(context \\ nil)

查看源代码 (宏)

以关键字列表的形式返回给定上下文的绑定。

在返回的结果中,键是变量名称,值是相应的变量值。

如果给定的 contextnil(默认情况下为),则返回当前上下文的绑定。

示例

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]
链接到此宏

dbg(code \\ quote do binding() end, options \\ [])

查看源代码 (自 1.14.0 起) (宏)

调试给定的 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 函数将被调用,其参数为 prependedargs

  1. code 的 AST
  2. options 的 AST
  3. 调用 dbg/2 的位置的 Macro.Env 环境

调试函数在编译时被调用,并且它也必须返回一个 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 接受的相同选项。

链接到此宏

def(call, expr \\ nil)

View Source (宏)

使用给定的名称和主体定义一个公有函数。

例子

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/1MyMath.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

使用 MapKeyword 来存储许多参数之间的区别在于 Keyword 的键

  • 必须是原子
  • 可以多次给出
  • 按开发人员指定的顺序排列

函数名

Elixir 中的函数和变量名必须以下划线或非大写或标题大小写的 Unicode 字母开头。它们可以继续使用 Unicode 字母、数字和下划线的序列。它们可以以 ?! 结尾。Elixir 的 命名约定 建议函数和变量名以 snake_case 格式编写。

rescue/catch/after/else

函数体支持 rescuecatchafterelse,就像 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(funs, opts)

View Source (宏)

定义一个委托给另一个模块的函数。

使用 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]
链接到此宏

defexception(fields)

View Source (宏)

定义一个异常。

异常是通过实现 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

上面的例子展示了自定义异常消息的首选策略。

链接到此宏

defguard(guard)

View Source (since 1.6.0) (宏)
@spec defguard(Macro.t()) :: Macro.t()

生成一个适合在守卫表达式中使用的宏。

如果定义使用不在保护范围内的表达式,它会在编译时引发异常,否则它会创建一个可以在保护范围内部或外部使用的宏。

请注意,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
链接到此宏

defguardp(guard)

View Source (since 1.6.0) (宏)
@spec defguardp(Macro.t()) :: Macro.t()

生成一个适合在守卫表达式中使用的私有宏。

如果定义使用不在保护范围内的表达式,它会在编译时引发异常,否则它会创建一个可以在当前模块的保护范围内部或外部使用的私有宏。

defmacrop/2 类似,defguardp/1 必须在其在当前模块中使用之前定义。

链接到此宏

defimpl(name, opts, do_block \\ [])

View Source (宏)

为给定的协议定义一个实现。

有关更多信息,请参阅 Protocol 模块。

链接到此宏

defmacro(call, expr \\ nil)

View Source (宏)

使用给定的名称和主体定义一个公有宏。

宏必须在其使用之前定义。

查看 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
链接到此宏

defmacrop(call, expr \\ nil)

View Source (宏)

使用给定的名称和主体定义一个私有宏。

私有宏只能从定义它们的同一个模块访问。

私有宏必须在其使用之前定义。

查看 defmacro/2 以了解更多信息,并查看 def/2 以了解有关命名和默认参数的规则。

链接到此宏

defmodule(alias, do_block)

View Source (宏)

使用给定的名称和内容定义一个模块。

此宏使用给定的 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

在上面的例子中,创建了两个模块 - FooFoo.Bar。当嵌套时,Elixir 会自动为内部模块创建一个别名,允许在定义它的同一个词法范围内访问第二个模块 Foo.Bar 作为 BarFoo 模块)。只有在通过别名定义嵌套模块时才会发生这种情况。

如果将 Foo.Bar 模块移到其他地方,则需要将 Foo 模块中对 Bar 的引用更新为完全限定的名称(Foo.Bar),或者必须使用 alias/2Foo 模块中显式设置别名。

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 保留以下模块名称:ElixirAnyBitStringPIDReference

链接到此宏

defoverridable(keywords_or_behaviour)

View Source (宏)

使当前模块中的给定定义可重写。

如果用户使用相同名称和元数定义新的函数或宏,则可覆盖的函数或宏将被丢弃。否则,将使用原始定义。

被覆盖的定义可能具有与原始定义不同的可见性:公有函数可以被私有函数覆盖,反之亦然。

宏不能被函数覆盖,反之亦然。

示例

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?/1function_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
链接到此宏

defp(call, expr \\ nil)

View Source (宏)

使用给定的名称和主体定义一个私有函数。

私有函数只能从定义它们的模块内部访问。尝试从定义私有函数的模块外部访问它会导致 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
链接到此宏

defprotocol(name, do_block)

View Source (宏)

定义一个协议。

有关更多信息,请参阅 Protocol 模块。

链接到此宏

defstruct(fields)

View Source (宏)

定义一个结构体。

结构体是一种带标签的映射,允许开发者为键提供默认值、用于多态分派的标签以及编译时断言。有关结构体的更多信息,请查看 %/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

链接到此宏

destructure(left, right)

View Source (宏)

解构两个列表,将右列表中的每个项分配给左列表中的匹配项。

与通过 = 进行模式匹配不同,如果左右列表的大小不匹配,解构将简单地停止,而不是引发错误。

示例

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(就像 = 运算符一样)。

@spec exit(term()) :: no_return()

使用给定的原因停止调用进程的执行。

由于评估此函数会导致进程终止,因此它没有返回值。

由编译器内联。

示例

当进程结束时,默认情况下它以 :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 进程将崩溃。

链接到此函数

function_exported?(module, function, arity)

查看源代码
@spec function_exported?(module(), atom(), arity()) :: boolean()

如果 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
链接到此宏

get_and_update_in(path, fun)

View Source (宏)

通过给定的 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
链接到此函数

get_and_update_in(data, keys, fun)

查看源代码
@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/0Access.key/2 等示例。

@spec get_in(Access.t(), [term(), ...]) :: term()

从嵌套结构中获取值。

使用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/0Access.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(condition, clauses)

View Source (宏)

提供一个 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(term, opts \\ [])

查看源代码
@spec inspect(
  Inspect.t(),
  keyword()
) :: String.t()

根据 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 协议文档中的“派生”部分。

链接到此函数

macro_exported?(module, macro, arity)

查看源代码
@spec macro_exported?(module(), atom(), arity()) :: boolean()

如果 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>
链接到此宏

match?(pattern, expr)

View Source (宏)

一个方便的宏,用于检查右侧(表达式)是否与左侧(模式)匹配。

例子

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}
@spec max(first, second) :: first | second when first: term(), second: term()

根据结构比较返回两个给定项中最大的项。

如果项比较相等,则返回第一个项。

这执行结构比较,其中所有 Elixir 项都可以相互比较。有关更多信息,请参阅 "结构比较" 部分 部分。

由编译器内联。

例子

iex> max(1, 2)
2
iex> max(:a, :b)
:b
@spec min(first, second) :: first | second when first: term(), second: term()

根据结构比较返回两个给定项中最小的项。

如果项比较相等,则返回第一个项。

这执行结构比较,其中所有 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}}}
链接到此函数

put_elem(tuple, index, value)

查看源代码
@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}
链接到此宏

put_in(path, value)

View Source (宏)

通过给定的 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}}
链接到此函数

put_in(data, keys, value)

查看源代码
@spec put_in(Access.t(), [term(), ...], term()) :: Access.t()

将值放入嵌套结构中。

使用 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
链接到此宏

raise(exception, attributes)

查看源代码 (宏)

引发异常。

在给定参数(必须是模块名称,如 ArgumentErrorRuntimeError)上调用 exception/1 函数,并传递 attributes 来检索异常结构体。

任何包含对 defexception/1 宏的调用的模块都会自动实现 Exception.exception/1 回调,该回调由 raise/2 预期。有关更多信息,请参见 defexception/1

示例

iex> raise(ArgumentError, "Sample")
** (ArgumentError) Sample
链接到此宏

reraise(message, stacktrace)

查看源代码 (宏)

引发异常并保留之前的堆栈跟踪。

类似于 raise/1,但不生成新的堆栈跟踪。

注意,可以在 catch/rescue 内部使用 __STACKTRACE__ 来检索当前堆栈跟踪。

示例

try do
  raise "oops"
rescue
  exception ->
    reraise exception, __STACKTRACE__
end
链接到此宏

reraise(exception, attributes, stacktrace)

查看源代码 (宏)

引发异常并保留之前的堆栈跟踪。

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(term, modifiers)

查看源代码 (宏)

处理字符列表的 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(term, modifiers)

查看源代码 (宏)

处理字符列表的 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(date_string, modifiers)

查看源代码 (宏)

处理日期的 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(naive_datetime_string, modifiers)

查看源代码 (宏)

处理朴素日期时间的 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(term, modifiers)

查看源代码 (宏)

处理正则表达式的 sigil ~r

它返回一个正则表达式模式,取消转义字符并替换插值。

有关正则表达式的更多信息,请参见 Regex 模块。

示例

iex> Regex.match?(~r/foo/, "foo")
true

iex> Regex.match?(~r/a#{:b}c/, "abc")
true

虽然 ~r sigil 允许使用括号和方括号作为分隔符,但建议使用 "/ 来避免与保留的正则表达式字符的转义冲突。

链接到此宏

sigil_S(term, modifiers)

查看源代码 (宏)

处理字符串的 sigil ~S

它返回一个不包含插值和转义字符的字符串,除了转义关闭的 sigil 字符本身。

示例

iex> ~S(foo)
"foo"
iex> ~S(f#{o}o)
"f\#{o}o"
iex> ~S(\o/)
"\\o/"

但是,如果您想在字符串上重新使用 sigil 字符本身,您需要对其进行转义

iex> ~S((\))
"()"
链接到此宏

sigil_s(term, modifiers)

查看源代码 (宏)

处理字符串的 sigil ~s

它返回一个字符串,就像它是一个双引号字符串一样,取消转义字符并替换插值。

示例

iex> ~s(foo)
"foo"

iex> ~s(f#{:o}o)
"foo"

iex> ~s(f\#{:o}o)
"f\#{:o}o"
链接到此宏

sigil_T(time_string, modifiers)

查看源代码 (宏)

处理时间的 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(datetime_string, modifiers)

查看源代码 (自 1.9.0 起) (宏)

处理 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(term, modifiers)

查看源代码 (宏)

处理单词列表的 sigil ~W

它返回一个由空格分隔的 "单词" 列表,不包含插值和转义字符,除了转义关闭的 sigil 字符本身。

修饰符

  • s: 列表中的单词是字符串(默认)
  • a: 列表中的单词是原子
  • c: 列表中的单词是字符列表

示例

iex> ~W(foo #{bar} baz)
["foo", "\#{bar}", "baz"]
链接到此宏

sigil_w(term, modifiers)

查看源代码 (宏)

处理单词列表的 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"]
@spec spawn((-> any())) :: pid()

生成给定的函数并返回其 PID。

通常,开发人员不使用 spawn 函数,而是使用诸如 TaskGenServerAgent 之类的抽象,这些抽象构建在 spawn 之上,这些抽象在进程自省和调试方面提供了更多便利。

查看 Process 模块以获取更多与进程相关的函数。

匿名函数接收 0 个参数,可以返回任何值。

由编译器内联。

示例

current = self()
child = spawn(fn -> send(current, {self(), 1 + 2}) end)

receive do
  {^child, 3} -> IO.puts("Received 3 back")
end
链接到此函数

spawn(module, fun, args)

查看源代码
@spec spawn(module(), atom(), list()) :: pid()

从给定的 module 生成给定的函数 fun,传递给定的 args 并返回其 PID。

通常,开发人员不使用 spawn 函数,而是使用诸如 TaskGenServerAgent 之类的抽象,这些抽象构建在 spawn 之上,这些抽象在进程自省和调试方面提供了更多便利。

查看 Process 模块以获取更多与进程相关的函数。

由编译器内联。

示例

spawn(SomeModule, :function, [1, 2, 3])
@spec spawn_link((-> any())) :: pid()

生成给定的函数,将其链接到当前进程,并返回其 PID。

通常,开发人员不使用 spawn 函数,而是使用诸如 TaskGenServerAgent 之类的抽象,这些抽象构建在 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
链接到此函数

spawn_link(module, fun, args)

查看源代码
@spec spawn_link(module(), atom(), list()) :: pid()

从给定的 module 生成给定的函数 fun,传递给定的 args,将其链接到当前进程,并返回其 PID。

通常,开发人员不使用 spawn 函数,而是使用诸如 TaskGenServerAgent 之类的抽象,这些抽象构建在 spawn 之上,这些抽象在进程自省和调试方面提供了更多便利。

查看 Process 模块以获取更多与进程相关的函数。有关链接的更多信息,请查看 Process.link/1

由编译器内联。

示例

spawn_link(SomeModule, :function, [1, 2, 3])
@spec spawn_monitor((-> any())) :: {pid(), reference()}

生成给定的函数,监控它并返回其 PID 和监控引用。

通常,开发人员不使用 spawn 函数,而是使用诸如 TaskGenServerAgent 之类的抽象,这些抽象构建在 spawn 之上,这些抽象在进程自省和调试方面提供了更多便利。

查看 Process 模块以获取更多与进程相关的函数。

匿名函数接收 0 个参数,可以返回任何值。

由编译器内联。

示例

current = self()
spawn_monitor(fn -> send(current, {self(), 1 + 2}) end)
链接到此函数

spawn_monitor(module, fun, args)

查看源代码
@spec spawn_monitor(module(), atom(), list()) :: {pid(), reference()}

生成给定的模块和函数,传递给定的参数,监控它并返回其 PID 和监控引用。

通常,开发人员不使用 spawn 函数,而是使用诸如 TaskGenServerAgent 之类的抽象,这些抽象构建在 spawn 之上,这些抽象在进程自省和调试方面提供了更多便利。

查看 Process 模块以获取更多与进程相关的函数。

由编译器内联。

示例

spawn_monitor(SomeModule, :function, [1, 2, 3])
链接到此函数

struct(struct, fields \\ [])

查看源代码
@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"}
链接到此函数

struct!(struct, fields \\ [])

查看源代码
@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},因此此函数将检查每个给定的键值是否属于结构。但是,更新结构不会强制执行键,因为键只在构建时强制执行;

链接到此宏

tap(value, fun)

查看源代码 (从 1.12.0 起) (宏)

将第一个参数 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)
链接到此宏

then(value, fun)

查看源代码 (从 1.12.0 起) (宏)

将第一个参数 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"]
@spec throw(term()) :: no_return()

从函数中进行非局部返回。

通常不建议使用 throw/1,因为它允许函数从其常规执行流程中退出,这会使代码更难阅读。此外,所有抛出的值都必须由 try/catch 捕获。有关更多信息,请参见 try/1

由编译器内联。

链接到此宏

to_charlist(term)

查看源代码 (宏)

根据 List.Chars 协议将给定的项转换为字符列表。

示例

iex> to_charlist(:foo)
~c"foo"

根据 String.Chars 协议将参数转换为字符串。

这是在进行字符串插值时调用的函数。

示例

iex> to_string(:foo)
"foo"
链接到此宏

unless(condition, clauses)

查看源代码 (宏)

提供一个 unless 宏。

此宏在 condition 计算为假值(falsenil)时,将评估并返回作为第二个参数传入的 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"
链接到此宏

update_in(path, fun)

查看源代码 (宏)

通过给定的 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}}
链接到此函数

update_in(data, keys, fun)

查看源代码
@spec update_in(Access.t(), [term(), ...], (term() -> term())) :: Access.t()

更新嵌套结构中的键。

使用 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(module, opts \\ [])

查看源代码 (宏)

在当前上下文中使用给定的模块。

当调用

use MyModule, some: :options

Elixir 将调用 MyModule.__using__/1,并将 use 的第二个参数作为其参数传递。由于 __using__/1 通常是一个宏,因此所有常见的宏规则都适用,其返回值应该是引用的代码,然后将其插入调用 use/2 的位置。

代码注入

use MyModule 在调用方充当**代码注入点**。鉴于调用方 use MyModule 对代码注入方式的控制有限,因此应该谨慎使用 use/2。如果可以,请尽可能避免使用,而是使用 import/2alias/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.
链接到此宏

var!(var, context \\ nil)

查看源代码 (宏)

标记给定的变量不应被卫生化。

此宏需要一个变量,它通常在 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"))

布尔“或”运算符。

提供一个短路运算符,仅当第一个表达式不计算为真值(即,它是 nilfalse)时才计算并返回第二个表达式。否则返回第一个表达式。

在保护子句中不允许。

示例

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 不同,此运算符接受任何表达式作为第一个参数,而不仅仅是布尔值。