elixir-lang.org 8 Modules
8 Module
在Elixir中,module是一系列函数的集合我们已经在先前的章节中使用过许多不同的module,比如 String module
iex> String.length "hello"
5
使用defmodule宏在Elixir定义自己的module,使用def宏定义函数,比如:
iex> defmodule Math do
...> def sum(a, b) do
...> a + b
...> end
...> end
iex> Math.sum(1, 2)
3
在下面的章节中,代码示例可以有点复杂,可能不太好在shell中测试这些代码,不过我们可以学习如何编译运行代码或者怎么以script的形式运行。
8.1 编译
通常我们会把一个module在一个文件中编写在以方便编译,重用,比如我们有一个math.ex 的Elixir代码文件,内容是这样的:
defmodule Math do
def sum(a, b) do
a + b
end
end
使用elixirc
这个工具进行编译:
elixirc math.ex
这个命令会生成 Elixir.Math.beam
文件,此文件是编译生成的字节码,我们的math module
应该就可以用了,启动iex
,注意要和beam
文件在同一个目录
iex> Math.sum(1, 2)
3
Elixir 项目通常将代码组织到三个目录 :
- ebin - 编译成的字节码
- lib - elixir代码,后缀.ex
- test - 测试文件 ,后缀.exs
在实际工程中,Elixir的构建工具mix
会帮你编译代码,设置paths。为了方便学习,Elixir也支持以脚本的形式运行你的代码。脚本形式不会生成编译文件。
8.2 脚本模式
除了Elixir的.ex
文件扩展,Elixir 也支持 .exs
扩展用于脚本模式。Elixir处理两中文件的方式基本相同,唯一不同的是脚本不需要编译。
.exs 后缀的文件是Elixir 脚本文件,意思是以脚本的方式运行Elixir代码, 例如:创建math.exs
文件
math.exs:
defmodule Math do
def sum(a, b) do
a + b
end
end
IO.puts to_string(Math.sum(1, 2))
这样执行
elixir math.exs
8.3 函数
在模块内,我们可以使用 def/2
定义函数,使用 defp/2
定义私有函数。使用def/2
定义的函数可以在其他module调用,私有函数只能module内调用, 比如:
defmodule Math do
def sum(a, b) do
do_sum(a, b)
end
defp do_sum(a, b) do
a + b
end
end
Math.sum(1, 2) #=> 3
Math.do_sum(1, 2) #=> ** (UndefinedFunctionError)
函数声明中同样支持卫语句与多子句,如果一个函数有多个子句(multiple clauses),Elixir会尝试每一个子句,直到有一个匹配。下边实现了个函数,检测参数是0还是其它的
defmodule Math do
def zero?(0) do
true
end
def zero?(x) when is_number(x) do
false
end
end
Math.zero?(0) #=> true
Math.zero?(1) #=> false
Math.zero?([1,2,3])
#=> ** (FunctionClauseError)
参数不匹配回报异常
8.4 函数捕获(Function capturing)
在本教程中,我们已经使用的符号 名字/参数数量(name/arity)
来引用函数。可以使用name/arity
形式的符号获取一个命名函数作为函数类型。打开你的iex,运行math.exs,命令是这样的:
$ iex math.exs
iex> Math.zero?(0)
true
iex> fun = &Math.zero?/1
&Math.zero?/1
iex> is_function fun
true
iex> fun.(0)
true
本地或者已经导入的函数,比如 is_function/1
,不用加module 名字就可以捕获:
iex> &is_function/1
&:erlang.is_function/1
iex> (&is_function/1).(fun)
true
注意capture语法也可以用作创建函数的快捷方式:
iex> fun = &(&1 + 1)
#Function<6.71889879/1 in :erl_eval.expr/5>
iex> fun.(1)
2
&1
表示传递到函数的第一个参数, &(&1+1)
展开就是 fn x -> x + 1
,你可以在Kernel.SpecialForms了解更多&
相关
8.5 默认参数
Elixir也支持默认参数
defmodule Concat do
def join(a, b, sep // " ") do
a <> sep <> b
end
end
IO.puts Concat.join("Hello", "world") #=> Hello world
IO.puts Concat.join("Hello", "world", "_") #=> Hello_world
任何表达式都可以用作默认值,但是表达式不会在定义的时候执行,函数调用的时候,默认参数值是表达式的话,会被执行
defmodule DefaultTest do
def dowork(x // IO.puts "hello") do
x
end
end
执行结果
iex> DefaultTest.dowork 123
123
iex> DefaultTest.dowork
hello
:ok
如果一个函数默认值有多个不同的子句(multiple clauses), 建议定义一个单独的头函数,此函数没有函数体,仅用作默认值的声明:
defmodule Concat do
def join(a, b // nil, sep // " ")
def join(a, b, _sep) when nil?(b) do
a
end
def join(a, b, sep) do
a <> sep <> b
end
end
IO.puts Concat.join("Hello", "world") #=> Hello world
IO.puts Concat.join("Hello", "world", "_") #=> Hello_world
IO.puts Concat.join("Hello") #=> Hello
定义函数使用默认值时,注意要避免交叉的函数定义,比如下面的例子,第二个join是sep是空的时候,会匹配到两个函数。
defmodule Concat do
def join(a, b) do
IO.puts "***First join"
a <> b
end
def join(a, b, sep // " ") do
IO.puts "***Second join"
a <> sep <> b
end
end
保存文件到concat.ex,编译,Elixir会有下面的warning:
concat.ex:7: this clause cannot match because a previous clause at line 2 always matches
编译器告诉我们当调用join这个函数时,如果只有两个参数,只会用到第一个join的定义,第二个只会在第三个参数存在的时候调用。
$ iex concat.ex
iex> Concat.join "Hello", "world"
***First join
"Helloworld"
iex> Concat.join "Hello", "world", "_"
***Second join
"Hello_world"
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。