#模塊函數
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中能夠從其餘模塊裏導入函數的詞彙命令,以及討論模塊的屬性。