Kernel.SpecialForms
Kernel.SpecialForms
特殊表单是Elixir的基本构建块,因此开发人员不能重写它。
我们在这个模块中定义它们。有些形式是词汇(如alias/2,case/2等)。宏{}和<<>>也用于分别限定元组和二进制数据结构特殊形式。
该模块还记录了返回有关Elixir的编译环境信息的宏,如(__ENV__/0
,__MODULE__/0
,__DIR__/0
和__CALLER__/0
)。
最后,它还记录了两种特殊形式,__block__/1
与__aliases__/1
,这些不打算由开发商直接调用,但它们出现在引用的内容中,因为它们在Elixir的构造中是必不可少的。
摘要
函数
%
创建一个结构
%{}
创建map
&(expr)
捕获或创建一个匿名函数
left . right
定义远程调用、对匿名函数的调用或别名。
left :: right
由类型和位字符串使用以指定类型。
<<args>>
定义新的位串。
left = right
将右边的值与左边的模式相匹配。
^var
在匹配子句中访问已绑定的变量。也被称为pin操作符
__CALLER__
以Macro.Env
结构形式返回当前调用环境
__DIR__
以二进制形式返回当前文件目录的绝对路径。
_ENV__
以Macro.Env
结构形式返回当前环境信息
__MODULE__
将当前模块名称作为原子返回或以其他方式返回nil
__aliases__(args)
保存别名信息的内部特殊形式
__block__(args)
块表达式的内部特殊形式
alias(module, opts)
alias/2
用于设置别名,通常用于模块名称。
case(condition, clauses)
将给定表达式与给定子句匹配。
cond(clauses)
计算与第一个子句对应的表达式,该表达式的计算值为真实值。
fn [clauses] end
定义匿名函数
for(args)
理解允许您快速地从可枚举或位字符串构建数据结构。
import(module, opts)
从其他模块导入函数和宏
quote(opts, block)
获取任何表达式的表示形式。
receive(args)
检查当前进程邮箱中是否存在匹配给定子句的消息。
require(module, opts)
需要一个模块才能使用它的宏。
super(args)
覆盖它时调用覆盖函数 Kernel.defoverridable/1
try(args)
计算给定表达式并处理可能发生的任何错误、退出或抛出。
unquote(expr)
从宏中取消引用给定表达式
unquote_splicing(expr)
取消引用扩展其参数的给定列表。类似于unquote/1
with(args)
用于组合匹配子句
{args}
创建元组
功能
% (macro)
创建一个结构。
struct是一个带标记的映射,允许开发人员为键、用于多态分派的标记和编译时断言提供默认值。
结构通常使用Kernel.defstruct/1
宏:
defmodule User do
defstruct name: "john", age: 27
end
现在可以创建如下结构:
%User{}
在一个结构体下方,只是一个带有:__struct__
指向User
模块的键的映射:
%User{} == %{__struct__: User, name: "john", age: 27}
结构也验证给定的键是定义的结构的一部分。下面的例子将失败,因为没有键:full_name
的User
结构:
%User{full_name: "john doe"}
还可以使用特定于结构的更新操作:
%User{user | age: 28}
上面的语法将保证给定的键在编译时有效,并且它将确保运行时给定的参数是一个结构,否则BadStructError
将失败。
虽然结构是map,但默认情况下,结构不会实现任何为map实现的协议。查看Kernel.defprotocol/2
更多关于结构如何与多态调度协议一起使用的信息。另请参阅Kernel.struct/2
以及Kernel.struct!/2
有关如何动态创建和更新结构的示例。
%{} (macro)
创建一个map。
查看Map
模块以获取更多关于地图的信息,它们的语法以及访问和操作它们的方法。
AST表示
无论=>是使用关键字语法还是使用关键字语法,为了简单起见,映射中的键值对总是在内部表示为两元素元组的列表:
iex> quote do
...> %{"a" => :b, c: :d}
...> end
{:%{}, [], [{"a", :b}, {:c, :d}]}
&(expr) (macro)
捕获或创建匿名函数。
俘获
捕获运算符最常用于从模块中捕获具有给定名称和模块的函数:
iex> fun = &Kernel.is_atom/1
iex> fun.(:atom)
true
iex> fun.("string")
false
在上面的例子中,我们捕获了Kernel.is_atom/1
然后调用它。
捕获操作符还可以通过省略模块名来捕获本地函数(包括私有函数)和导入函数:
&local_function/1
匿名函数
捕获操作符也可用于部分应用函数,其中&1
,&2
等等,可以用作值占位符。例如:
iex> double = &(&1 * 2)
iex> double.(2)
4
换句话说,&(&1 * 2)相当于fn x -> x * 2 end。使用本地函数的另一个示例:
iex> fun = &is_atom(&1)
iex> fun.(:atom)
true
该&
运算符可以用更复杂的表达式中使用:
iex> fun = &(&1 + &2 + &3)
iex> fun.(1, 2, 3)
6
以及列表和元组:
iex> fun = &{&1, &2}
iex> fun.(1, 2)
{1, 2}
iex> fun = &[&1 | &2]
iex> fun.(1, 2)
[1 | 2]
创建匿名函数的唯一限制是至少有一个占位符必须存在,即它必须至少包含&1
,并且不支持该块表达式:
# No placeholder, fails to compile.
&(:foo)
# Block expression, fails to compile.
&(&1; &2)
left . right (macro)
定义远程调用、对匿名函数的调用或别名。
Elixir中的dot(.
)可用于远程调用:
iex> String.downcase("FOO")
"foo"
在上面的这个例子中,我们使用了. 在String模块中调用downcase,传递“FOO”作为参数。
点也可用于调用匿名函数:
iex> (fn(n) -> n end).(7)
7
在这种情况下,左侧有一个功能。
我们还可以使用点创建别名:
iex> Hello.World
Hello.World
这一次,我们加入了两个别名,定义了最终的别名。Hello.World
...
句法
右面.
可以是以大写开头的单词,表示别名、以小写或下划线开头的单词、任何有效的语言运算符或以单引号或双引号包装的任何名称。这些都是有效的例子:
iex> Kernel.Sample
Kernel.Sample
iex> Kernel.length([1, 2, 3])
3
iex> Kernel.+(1, 2)
3
iex> Kernel."length"([1, 2, 3])
3
iex> Kernel.'+'(1, 2)
3
请注意,Kernel."FUNCTION_NAME"
将被视为远程呼叫而不是别名。这种选择是在每次使用单引号或双引号时完成的,无论引用内容如何,我们都有远程调用。这个决定也反映在下面讨论的引用表达式中。
当点用来调用一个匿名函数时,只有一个操作数,但仍然使用后缀表示法编写:
iex> negate = fn(n) -> -n end
iex> negate.(7)
-7
引用的表达
当.
被使用时,引用的表达式可能有两种截然不同的形式。当右侧以小写字母(或下划线)开头时:
iex> quote do
...> String.downcase("FOO")
...> end
{{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}, [], ["FOO"]}
请注意,我们有一个内部元组,其中包含:.
代表点的原子作为第一个元素:
{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}
该元组遵循Elixir中通用的引用表达式结构,名称作为第一个参数,一些关键字列表作为元数据作为第二个参数,而参数列表作为第三个参数。在这种情况下,参数是别名String
和原子:downcase
。远程调用中的第二个参数始终
是一个原子,而不管在调用中使用的文字是什么:
iex> quote do
...> String."downcase"("FOO")
...> end
{{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}, [], ["FOO"]}
包含的元组:.
被包装在另一个元组中,该元组实际表示函数调用,并且具有"FOO"
作为参数。
在调用匿名函数的情况下,具有点特殊形式的内部元组只有一个参数,这反映了操作符是一元的事实:
iex> quote do
...> negate.(0)
...> end
{{:., [], [{:negate, [], __MODULE__}]}, [], [0]}
当右侧是别名(即以大写字母开头)时,我们得到:
iex> quote do
...> Hello.World
...> end
{:__aliases__, [alias: false], [:Hello, :World]}
我们在__aliases__/1
特殊格式文档中详细介绍别名。
不引用
我们还可以使用unquote在带引号的表达式中生成远程调用:
iex> x = :downcase
iex> quote do
...> String.unquote(x)("FOO")
...> end
{{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}, [], ["FOO"]}
类似于Kernel."FUNCTION_NAME"
,unquote(x)
将始终生成一个远程调用,独立于该值x
。要通过引用的表达式生成别名,需要依赖Module.concat/2
:
iex> x = Sample
iex> quote do
...> Module.concat(String, unquote(x))
...> end
{{:., [], [{:__aliases__, [alias: false], [:Module]}, :concat]}, [],
[{:__aliases__, [alias: false], [:String]}, Sample]}
left :: right (macro)
由类型和位串使用来指定类型。
这个操作符在Elixir的两个不同的场合使用。它用于类型描述来指定变量,函数或类型本身的类型:
@type number :: integer | float
@spec add(number, number) :: number
它也可以用于位串以指定给定位段的类型:
<<int::integer-little, rest::bits>> = bits
阅读Typespec页面上的文档,<<>>/1并分别获取关于类型规范和位串的更多信息。
<<args>> (macro)
定义一个新的位串。
实例
iex> <<1, 2, 3>>
<<1, 2, 3>>
类型
位串由多个段组成,每个段都有一个类型。在位字符串中使用了9种类型:
integer
float
bits
(bitstring
的别名)
bitstring
binary
bytes
(binary
的别名)
utf8
utf16
utf32
当没有指定类型时,默认值是integer
:
iex> <<1, 2, 3>>
<<1, 2, 3>>
Elixir还默认接受该段为文字字符串或文字charlist,默认情况下该段扩展为整数:
iex> <<0, "foo">>
<<0, 102, 111, 111>>
变量或任何其他类型都需要显式标记:
iex> rest = "oo"
iex> <<102, rest>>
** (ArgumentError) argument error
我们可以通过显式地将其标记为binary
*
iex> rest = "oo"
iex> <<102, rest::binary>>
"foo"
这些utf8
,utf16
和utf32
类型是用于Unicode码点的。它们也可以应用于字符串和查询表:
iex> <<"foo"::utf16>>
<<0, 102, 0, 111, 0, 111>>
iex> <<"foo"::utf32>>
<<0, 0, 0, 102, 0, 0, 0, 111, 0, 0, 0, 111>>
备选方案
可以通过使用-
作为隔膜。顺序是任意的,因此以下所有内容都是等价的:
<<102::integer-native, rest::binary>>
<<102::native-integer, rest::binary>>
<<102::unsigned-big-integer, rest::binary>>
<<102::unsigned-big-integer-size(8), rest::binary>>
<<102::unsigned-big-integer-8, rest::binary>>
<<102::8-integer-big-unsigned, rest::binary>>
<<102, rest::binary>>
单位和大小
匹配的长度等于unit
(size
长度重复段的数量)乘以(位数)倍unit
。
类型 | 默认单位 |
---|---|
整数 | 1位 |
浮动 | 1位 |
二进制 | 8位 |
类型的大小稍微有点细微差别。整数的默认大小是8。
对于浮点数,它是64.对于浮点数,size * unit
必须为32或64,分别对应于IEEE 754 binary32和binary64。
对于二进制文件,缺省值是二进制文件的大小。只有匹配中的最后一个二进制文件才能使用默认大小。其他所有的必须显式指定它们的大小,即使匹配是明确的。例如:
iex> <<name::binary-size(5), " the ", species::binary>> = <<"Frank the Walrus">>
"Frank the Walrus"
iex> {name, species}
{"Frank", "Walrus"}
未能指定非最后一次的大小会导致编译失败:
<<name::binary, " the ", species::binary>> = <<"Frank the Walrus">>
** (CompileError): a binary field without size is only allowed at the end of a binary pattern
快捷语法
在传递整数值时,还可以使用语法快捷方式指定大小和单位:
iex> x = 1
iex> <<x::8>> == <<x::size(8)>>
true
iex> <<x::8*4>> == <<x::size(8)-unit(4)>>
true
这个语法反映了一个事实,即有效的大小是通过单位乘以大小来确定的。
修饰符
有些类型有关联的修饰符来清除字节表示中的歧义。
模式 | 相关类型 |
---|---|
signed | integer |
unsigned (default) | integer |
little | integer, float, utf16, utf32 |
big (default) | integer, float, utf16, utf32 |
native | integer, utf16, utf32 |
标志
整数可以是,signed
或unsigned
,默认为unsigned
。
iex> <<int::integer>> = <<-100>>
<<156>>
iex> int
156
iex> <<int::integer-signed>> = <<-100>>
<<156>>
iex> int
-100
signed
与unsigned
仅用于匹配二进制文件(见下文),仅用于整数。
iex> <<-100::signed, _rest::binary>> = <<-100, "foo">>
<<156, 102, 111, 111>>
字节序
Elixir对字节序有三个选项:big
,little
,和native
。默认值是big
:
iex> <<number::little-integer-size(16)>> = <<0, 1>>
<<0, 1>>
iex> number
256
iex> <<number::big-integer-size(16)>> = <<0, 1>>
<<0, 1>>
iex> number
1
native
由VM在启动时确定并取决于主机操作系统。
二进制/位串匹配
二进制匹配是Elixir的一个强大特性,它有助于从二进制文件中提取信息以及模式匹配。
二进制匹配本身可以用于从二进制文件中提取信息:
iex> <<"Hello, ", place::binary>> = "Hello, World"
"Hello, World"
iex> place
"World"
或者作为模式匹配函数定义的一部分:
defmodule ImageTyper
@png_signature <<137::size(8), 80::size(8), 78::size(8), 71::size(8),
13::size(8), 10::size(8), 26::size(8), 10::size(8)>>
@jpg_signature <<255::size(8), 216::size(8)>>
def type(<<@png_signature, rest::binary>>), do: :png
def type(<<@jpg_signature, rest::binary>>), do: :jpg
def type(_), do :unknown
end
性能与优化
Erlang编译器可以对二进制创建和匹配提供许多优化。要查看优化输出,请设置bin_opt_info
编译器选项:
ERL_COMPILER_OPTIONS=bin_opt_info mix compile
要了解更多关于特定优化和性能考虑事项,请查看Erlang的效率指南处理二进制文件。
left = right (macro)
将右边的值与左边的模式相匹配。
^var (macro)
在匹配子句中访问已绑定的变量。也被称为pin操作符。
实例
Elixir允许变量通过静态单一赋值来反弹:
iex> x = 1
iex> x = x + 1
iex> x
2
但是,在某些情况下,与现有值匹配而不是重新绑定是有用的。这可以通过^
特殊形式,俗称针操作符:
iex> x = 1
iex> ^x = List.first([1])
iex> ^x = List.first([2])
** (MatchError) no match of right hand side value: 2
请注意,^x
始终是指x
匹配前的值。以下示例将匹配:
iex> x = 0
iex> {x, ^x} = {1, 0}
iex> x
1
_CALLER__ (macro)
将当前调用环境作为Macro.Env
结构。
在这个环境中,您可以访问文件名、行号、设置别名、函数和其他。
__DIR__ (macro)
以二进制形式返回当前文件目录的绝对路径。
虽然目录可以访问为Path.dirname(__ENV__.file)
,这个宏是一个方便的快捷方式。
__ENV__ (macro)
将当前环境信息作为Macro.Env
结构。
在该环境中,您可以访问当前文件名、行号、设置别名、当前函数和其他。
__MODULE__ (macro)
将当前模块名称作为原子返回,否则返回零。
虽然模块可以在中访问,但__ENV__/0
这个宏是一个方便的捷径。
_aliases__(args) (macro)
内部特殊形式,以保存别名信息。
它通常被编译成一个原子:
iex> quote do
...> Foo.Bar
...> end
{:__aliases__, [alias: false], [:Foo, :Bar]}
Elixir代表Foo.Bar
作为__aliases__
这样的呼叫可以由操作员清楚地识别:.
。例如:
iex> quote do
...> Foo.bar
...> end
{{:., [], [{:__aliases__, [alias: false], [:Foo]}, :bar]}, [], []}
每当表达式迭代器看到一个:。 作为元组键,可以确定它代表一个调用,并且列表中的第二个参数是一个原子。
另一方面,别名具有一些属性:
- 别名的头元素可以是在编译时必须扩展到原子的任何术语。
- 别名的尾部元素保证始终是原子。
3. 当别名的头元素是原子时:Elixir
,不会发生扩展。
- 元组的第一个元素始终是同一表示中的原子或另一个元组。
2. 元组的第二个元素表示元数据。
3. 元组的第三个元素是函数调用的参数。第三个参数可能是一个原子,它通常是一个变量(或一个本地调用)。
选项
- ::unquote - 如果为false,则禁用取消引用。 当你在另一个报价单内有一个报价并想要控制报价能够引用时很有用。
- ::location- 当设置为:keep,保持当前行和文件不被引用。 阅读下面的Stacktrace信息部分以获取更多信息。
:generated
- 将给定的块标记为已生成,因此不会发出警告。目前它只适用于特殊形式(例如,您可以注释case
但不是if
)。
:context
- 设置分辨率上下文。
:bind_quoted
-将绑定传递到宏。无论何时给出绑定,unquote/1
自动禁用。
:infinity
-进程应无限期地等待匹配消息,这与不使用超时相同
0
- 如果邮箱中没有匹配的邮件,超时将立即发生
- 小于
4_294_967_295
(0xFFFFFFFF
以十六进制表示法)的正整数- 应该可以将超时值表示为无符号的32位整数。
变量处理
该receive/1
特殊形式处理变量完全一样的case/2
特殊的宏。有关更多信息,请查看文档case/2
。
require(module, opts) (macro)
需要一个模块才能使用它的宏。
实例
模块中的公共函数是全局可用的,但是为了使用宏,您需要通过要求在其中定义模块来选择。
假设您if/2
在模块中创建了自己的实现MyMacros
。如果你想调用它,你需要首先明确地要求MyMacros
:
defmodule Math do
require MyMacros
MyMacros.if do_something, it_works
end
试图调用未加载的宏将引发错误。
别名捷径
require/2
也as:
作为一个选项接受,所以它会自动设置一个别名。请检查alias/2
更多信息。
super(args) (macro)
覆盖它时调用覆盖函数Kernel.defoverridable/1
。
查看Kernel.defoverridable/1
了解更多信息和文档。
try(args) (macro)
评估给定的表达式并处理任何错误,退出或抛出可能发生的错误。
实例
try do
do_something_that_may_fail(some_arg)
rescue
ArgumentError ->
IO.puts "Invalid argument given"
catch
value ->
IO.puts "Caught #{inspect(value)}"
else
value ->
IO.puts "Success! The result was #{inspect(value)}"
after
IO.puts "This is printed regardless if it failed or succeed"
end
该rescue
子句用于处理异常,而catch
子句可用于捕获抛出的值和退出。该else
子句可用于基于表达式的结果控制流。catch
,rescue
和else
子句基于模式匹配工作(类似于case
特殊形式)。
请注意,内部调用try/1
不是尾递归的,因为VM需要保持堆栈跟踪,以防出现异常。
rescue 子句
除了依赖模式匹配之外,rescue
子句提供了一些便利,例如允许用名称拯救异常的异常。以下所有格式都是rescue
条款中的有效模式:
try do
UndefinedModule.undefined_function
rescue
UndefinedFunctionError -> nil
end
try do
UndefinedModule.undefined_function
rescue
[UndefinedFunctionError] -> nil
end
# rescue and bind to x
try do
UndefinedModule.undefined_function
rescue
x in [UndefinedFunctionError] -> nil
end
# rescue all and bind to x
try do
UndefinedModule.undefined_function
rescue
x -> nil
end
Erlang错误
Erlang错误在拯救时转化为Elixir的错误:
try do
:erlang.error(:badarg)
rescue
ArgumentError -> :ok
end
最常见的Erlang错误将被转化为他们的Elixir对手部分。那些不会被转化为ErlangError
:
try do
:erlang.error(:unknown)
rescue
ErlangError -> :ok
end
事实上,ErlangError
可以用来拯救任何不是适当的Elixir错误。例如,:badarg
在转换之前,它可以用来拯救更早的错误:
try do
:erlang.error(:badarg)
rescue
ErlangError -> :ok
end
捕捉抛出和退出
该catch
子句可用于捕获抛出的值和退出。
try do
exit(:shutdown)
catch
:exit, :shutdown ->
IO.puts "Exited with shutdown reason"
end
try do
throw(:sample)
catch
:throw, :sample ->
IO.puts ":sample was thrown"
end
该catch
条款还支持:error
一起:exit
和:throw
,在二郎,虽然它支持常用避免raise
/ rescue
控制机制。其中一个原因是,在捕获时:error
,错误不会自动转化为Elixir错误:
try do
:erlang.error(:badarg)
catch
:error, :badarg ->
:ok
end
请注意,有可能匹配捕获值以及这种值的种类
:
try do
exit(:shutdown)
catch
kind, value when kind in [:exit, :throw] ->
IO.puts "Exited with or thrown value #{inspect(value)}"
end
after 子句
一个after
子句允许你定义清理逻辑,当代码的成功执行结束时以及发生错误时,这些清理逻辑都会被调用。请注意,进程将通常在收到退出信号时退出,导致其突然退出,因此after
不保证子句被执行。幸运的是,Elixir中的大多数资源(例如打开文件,ETS表,端口,套接字等)都会链接到或监视拥有的进程,并在进程退出时自动清理自己。
File.write!("tmp/story.txt", "Hello, World")
try do
do_something_with("tmp/story.txt")
after
File.rm("tmp/story.txt")
end
else 子句
else
子句允许尝试表达式的结果在以下模式上匹配:
x = 2
try do
1 / x
rescue
ArithmeticError ->
:infinity
else
y when y < 1 and y > -1 ->
:small
_ ->
:large
end
如果else
子句不存在,也不引发异常,表达式的结果将被返回:
x = 1
^x =
try do
1 / x
rescue
ArithmeticError ->
:infinity
end
但是,如果存在else
子句但表达式的结果与任何模式都不匹配,则会引发异常。这个例外不会被一个catch
或者rescue
同一个人发现try
:
x = 1
try do
try do
1 / x
rescue
# The TryClauseError can not be rescued here:
TryClauseError ->
:error_a
else
0 ->
:small
end
rescue
# The TryClauseError is rescued here:
TryClauseError ->
:error_b
end
同样,一个else
子句中的异常不会在同一个内部被捕获或获救try
:
try do
try do
nil
catch
# The exit(1) call below can not be caught here:
:exit, _ ->
:exit_a
else
_ ->
exit(1)
end
catch
# The exit is caught here:
:exit, _ ->
:exit_b
end
这意味着VM不再需要将堆栈跟踪保存在else
子句,所以当使用try
中的尾调用作为最后的调用。else
子句。同样的道理也适用于rescue
和catch
子句。
只有尝试表达式的结果才会下降到else
子句。如果try
最后在rescue
或catch
子句,它们的结果不会下降到else
*
try do
throw(:catch_this)
catch
:throw, :catch_this ->
:it_was_caught
else
# :it_was_caught will not fall down to this "else" clause.
other ->
{:else, other}
end
变量处理
因为里面的表达式try
可能由于异常而没有计算,在其中创建的任何变量try
无法从外部访问。例如:
try do
x = 1
do_something_that_may_fail(same_arg)
:ok
catch
_, _ -> :failed
end
x #=> unbound variable "x"
在上面的例子中,x
由于它是在try
子句中定义的,因此无法访问。解决此问题的常见做法是返回内部定义的变量try
:
x =
try do
x = 1
do_something_that_may_fail(same_arg)
x
catch
_, _ -> :failed
end
unquote(expr) (macro)
从宏中取消引用给定表达式。
实例
想象一下你有一个变量的情况value
,你想把它注入一些引用。第一次尝试将是:
value = 13
quote do
sum(1, value, 3)
end
然后返回:
{:sum, [], [1, {:value, [], quoted}, 3]}
这不是预期的结果。为此,我们使用以下引号:
iex> value = 13
iex> quote do
...> sum(1, unquote(value), 3)
...> end
{:sum, [], [1, 13, 3]}
unquote_splicing(expr) (macro)
没有引用扩展其参数的给定列表。类似于unquote/1
。
实例
iex> values = [2, 3, 4]
iex> quote do
...> sum(1, unquote_splicing(values), 5)
...> end
{:sum, [], [1, 2, 3, 4, 5]}
with(args) (macro)
用于组合匹配子句。
让我们从一个例子开始:
iex> opts = %{width: 10, height: 15}
iex> with {:ok, width} <- Map.fetch(opts, :width),
...> {:ok, height} <- Map.fetch(opts, :height),
...> do: {:ok, width * height}
{:ok, 150}
如果所有子句匹配,则do
块将被执行,并返回其结果。否则,链将被中止,并返回不匹配的值:
iex> opts = %{width: 10}
iex> with {:ok, width} <- Map.fetch(opts, :width),
...> {:ok, height} <- Map.fetch(opts, :height),
...> do: {:ok, width * height}
:error
守卫也可以用于模式:
iex> users = %{"melany" => "guest", "bob" => :admin}
iex> with {:ok, role} when not is_binary(role) <- Map.fetch(users, "bob"),
...> do: {:ok, to_string(role)}
{:ok, "admin"}
和for/1
一样,里面的变量with/1
不会泄漏; 条款之间也可以插入“裸露的表达”:
iex> width = nil
iex> opts = %{width: 10, height: 15}
iex> with {:ok, width} <- Map.fetch(opts, :width),
...> double_width = width * 2,
...> {:ok, height} <- Map.fetch(opts, :height),
...> do: {:ok, double_width * height}
{:ok, 300}
iex> width
nil
else
可以给出一个选项来修改with
在匹配失败的情况下返回的内容:
iex> opts = %{width: 10}
iex> with {:ok, width} <- Map.fetch(opts, :width),
...> {:ok, height} <- Map.fetch(opts, :height) do
...> {:ok, width * height}
...> else
...> :error ->
...> {:error, :wrong_data}
...> end
{:error, :wrong_data}
如果没有匹配else
条件,则引发WithClauseError
异常。
{args} (macro)
创建一个元组。
有关元组数据类型和操作元组的函数的更多信息可以在Tuple
模块中找到; 一些用于处理元组的函数也可以在Kernel
(如Kernel.elem/2
or Kernel.tuple_size/1
)中使用。
AST表示
只有两项元组在Elixir中被视为文字,并在引用时返回自己。因此,所有其他元组都在AST中表示为对:{}
特殊表单的调用。
iex> quote do
...> {1, 2}
...> end
{1, 2}
iex> quote do
...> {1, 2, 3}
...> end
{:{}, [], [1, 2, 3]}