Elixir学习笔记——模块和函数

在 Elixir 中,我们将多个函数分组到模块中。在前面的章节中,我们已经使用了许多不同的模块,例如 String 模块:

iex> String.length("hello")
5

为了在 Elixir 中创建我们自己的模块,我们使用 defmodule 宏。模块的首字母必须为大写。我们使用 def 宏来定义该模块中的函数。每个函数的首字母必须为小写(或下划线):

iex> defmodule Math do
...  >   def sum(a, b) do
...  >    a + b
...  >  end
...  >end

iex> Math.sum(1, 2)
3

在本章中,我们将定义我们自己的模块,具有不同的复杂程度。随着我们的示例越来越长,在 shell 中输入它们可能会很棘手。现在是时候学习如何编译 Elixir 代码以及如何运行 Elixir 脚本了。

编译

大多数情况下,将模块写入文件以便编译和重用是很方便的。假设我们有一个名为 math.ex 的文件,内容如下:

defmodule Math do
  def sum(a, b) do
    a + b
  end
end

可以使用 elixirc 编译此文件:

$ elixirc math.ex

这将生成一个名为 Elixir.Math.beam 的文件,其中包含已定义模块的字节码。如果我们再次启动 iex,我们的模块定义将可用(前提是 iex 在字节码文件所在的同一目录中启动):

iex> Math.sum(1, 2)
3

Elixir 项目通常组织成三个目录:

1._build         - 包含编译工件
2.lib               - 包含 Elixir 代码(通常是 .ex 文件)
3.test             - 包含测试(通常是 .exs 文件)

在实际项目上工作时,名为 mix 的构建工具将负责为您编译和设置正确的路径。为了学习和方便起见,Elixir 还支持更灵活且不会生成任何编译工件的脚本模式。

脚本模式

除了 Elixir 文件扩展名 .ex 之外,Elixir 还支持 .exs 文件用于脚本编写。Elixir 对待这两个文件的方式完全相同,唯一的区别在于意图。.ex 文件用于编译,而 .exs 文件用于脚本编写。mix 等项目遵循此惯例。

例如,我们可以创建一个名为 math.exs 的文件:

defmodule Math do
  def sum(a, b) do
    a + b
  end
end

IO.puts Math.sum(1, 2)

并按如下方式执行:

$ elixir math.exs
因为我们使用的是 elixir 而不是 elixirc,所以模块已编译并加载到内存中,但没有将 .beam 文件写入磁盘。在以下示例中,我们建议您将代码写入脚本文件并按上述方式执行。

函数定义

在模块内部,我们可以使用 def/2 定义函数,使用 defp/2 定义私有函数。使用 def/2 定义的函数可以从其他模块调用,而私有函数只能在本地调用。

defmodule Math do
  def sum(a, b) do
    do_sum(a, b)
  end

  defp do_sum(a, b) do
    a + b
  end
end

IO.puts Math.sum(1, 2)    #=> 3
IO.puts Math.do_sum(1, 2) #=> ** (UndefinedFunctionError)

函数声明还支持保护和多个子句。如果函数有多个子句,Elixir 将尝试每个子句,直到找到匹配的子句。以下是检查给定数字是否为零的函数的实现:

defmodule Math do
  def zero?(0) do
    true
  end

  def zero?(x) when is_integer(x) do
    false
  end
end

IO.puts Math.zero?(0)         #=> true
IO.puts Math.zero?(1)         #=> false
IO.puts Math.zero?([1, 2, 3]) #=> ** (FunctionClauseError)
IO.puts Math.zero?(0.0)       #=> ** (FunctionClauseError)

zero? 后面的问号表示此函数返回布尔值。要了解有关 Elixir 中模块、函数名称、变量等的命名约定的更多信息,请参阅命名约定。

给出与任何子句都不匹配的参数会引发错误。

与 if 等结构类似,函数定义支持 do: 和 do-block 语法,正如我们在上一章中学到的那样。例如,我们可以将 math.exs 编辑为如下所示:

defmodule Math do
  def zero?(0), do: true
  def zero?(x) when is_integer(x), do: false
end

它将提供相同的行为。您可以将 do: 用于单行代码,但始终将 do-block 用于跨多行的函数。如果您希望保持一致,则可以在整个代码库中使用 do-block。

默认参数

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 \\ "hello") do
    x
  end
end

iex> DefaultTest.dowork()
"hello"
iex> DefaultTest.dowork(123)
123
iex> DefaultTest.dowork()
"hello"

如果具有多个默认值的函数,则需要创建一个函数头(没有主体的函数定义)来声明默认值:

defmodule Concat do
  # A function head declaring defaults
  def join(a, b \\ nil, sep \\ " ")

  def join(a, b, _sep) when is_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

当函数或子句未使用变量时,我们会在其名称前添加一个下划线 (_) 来表示此意图。我们的命名约定文档中也涵盖了此规则。

使用默认值时,必须小心避免函数定义重叠。请考虑以下示例:

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

Elixir 将发出以下警告:

warning: this clause cannot match because a previous clause at line 2 always matches
    concat.ex:7: Concat

编译器告诉我们,使用两个参数调用 join 函数将始终选择 join 的第一个定义,而第二个定义仅在传递三个参数时才会被调用:

$ iex concat.ex
Concat.join "Hello", "world"
***First join
"Helloworld"

Concat.join "Hello", "world", "_"
***Second join
"Hello_world"

在这种情况下删除默认参数将修复警告。

至此,我们对模块的简短介绍就结束了。在接下来的章节中,我们将学习如何使用函数定义进行递归,并随后探索与模块相关的更多功能。

  • 13
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值