elixir官方入門教程 模塊

#模塊函數

1. 編譯
2. 腳本模式
3. 具名函數
4. 函數捕獲
5. 默認參數

在Elixir中咱們將一些函數集合到模塊裏。在以前的章節裏咱們已經使用了許多不一樣的模塊,例如String模塊:工具

iex> String.length("hello")
5

爲了創造咱們本身的模塊,須要用到defmodule宏。咱們使用def宏來定義模塊中的函數:學習

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

iex> Math.sum(1, 2)
3

在接下來的部分,咱們的例子會變得更長,若把它們所有輸入終端則會變得很複雜。是時候學習如何編譯Elixir代碼以及如何運行Elixir腳本了。測試

#編譯this

大多數時候咱們都會將模塊寫入文件,方便編譯和複用。假設咱們有一個名爲math.ex的文件,內容以下:code

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

這個文件能夠用elixirc來編譯:orm

$ elixirc math.ex

這將會生成一個名爲Elixir.Math.beam的文件,包含了已定義模塊的字節碼。若是咱們從新啓動iex,將可使用咱們的模塊定義(須要在字節碼文件存在的目錄中啓動iex):遞歸

iex> Math.sum(1, 2)
3

Elixir工程一般由三個目錄組成:內存

- ebin —— 包含了編譯好的字節碼
- lib —— 包含了elixir代碼(一般是`.ex`文件)
- test —— 包含了測試文件(一般是`.exs`文件)

在實踐中,構建工具mix將會爲你編譯和設置好路徑。出於學習目的,Elixir也支持腳本模式,它更加靈活而且不會生成任何編譯後的火星文字。ci

#腳本模式

除了後綴名爲.ex的Elixir文件,Elixir也支持用於執行腳本的.exs文件。Ellixir對待它們幾乎徹底同樣,惟一的不一樣是目的。.ex文件須要被編譯,.exs文件用於執行腳本。在執行時,它們都會被編譯並將它們的模塊載入到內存裏,儘管只有.ex文件會將它的字節碼以.beam格式寫入硬盤。

舉個例子,咱們能夠創造一個名爲math.exs的文件:

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

IO.puts Math.sum(1, 2)

執行它:

$ elixir math.exs

文件會被在內存中編譯並執行,打印「3」做爲結果。不會建立字節碼文件。在接下來的例子中,咱們建議你將代碼寫入腳本文件並以上述方法執行。

#具名函數

在模塊中,咱們可使用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)

函數聲明也支持衛語句和多重從句。若是一個函數有多個從句,Elicir會逐個嘗試知道有一個匹配。下面定義了一個檢查數字是否爲零的函數:

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)

對於不匹配任何從句的參數會拋出一個異常。

if結構類似,具名函數也支持do:do/end塊語法,咱們已經知道do/end語法只不過是關鍵字列表的簡寫形式。例如,咱們能夠這樣修改math.exs文件:

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

它們效果是同樣的。你能夠用do:來寫一行的代碼,但對於多行代碼仍是要用do/end

#函數捕獲

在前文中,咱們一直使用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,能夠脫離模塊被捕獲:

iex> &is_function/1
&:erlang.is_function/1
iex> (&is_function/1).(fun)
true

注意捕獲語法也能夠用於建立函數:

iex> fun = &(&1 + 1)
#Function<6.71889879/1 in :erl_eval.expr/5>
iex> fun.(1)
2

&1表明傳遞給函數的第一個參數。&(&1 + 1)等同於fn x -> x + 1 end。這種語法很適用於短的函數定義。

若是你想捕獲一個模塊中的函數,你可使用&Module.function()

iex> fun = &List.flatten(&1, &2)
&List.flatten/2
iex> fun.([1, [[2], 3]], [4, 5])
[1, 2, 3, 4, 5]

&List.flatten(&1, &2)等同於fn(list, tail) -> List.flatten(list, tail) end,在這種狀況下和&List.flatten/2是同樣的。你能夠在Kernel.SpecialForms文檔中找到更多有關捕獲符號&的信息。

#默認參數

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
hello
:ok
iex> DefaultTest.dowork 123
123
iex> DefaultTest.dowork
hello
:ok

當帶默認值的函數有多個從句時,就須要建立一個不包含函數內容的函數頭來聲明默認值:

defmodule Concat do
  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

若是咱們將上述代碼保存到文件「concat.ex」中並編譯,Elixir將提出警告:

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

編譯器告訴咱們當用兩個參數調用join函數時,總會使用第一個join函數定義,而第二個定義只有當傳遞三個參數時纔會被調用:

$ iex concat.exs
iex> Concat.join "Hello", "world"
***First join
"Helloworld"
iex> Concat.join "Hello", "world", "_"
***Second join
"Hello_world"

對於模塊的介紹到此結束。下一章,咱們將學習如何使用具名函數進行遞歸,探索Elixir中能夠從其餘模塊裏導入函數的詞彙命令,以及討論模塊的屬性。

相關文章
相關標籤/搜索