13-別名和程式碼引用

StraightDave發表於2014-09-17

13-別名和程式碼引用

別名
require
import
別名機制
巢狀

為了實現軟體重用,Elixir提供了三種指令(directives)。之所以稱之為“指令”是因為它們的作用域是詞法作用域(lexical scope)。

13.1-別名

巨集alias可以為任何模組名設定別名。想象一下Math模組,它針對特殊的數學運算使用了特殊的列表實現:

defmodule Math do
  alias Math.List, as: List
end

現在,任何對List的引用將被自動變成對Math.List的引用。 如果還想訪問原來的List,需要字首'Elixir':

List.flatten             #=> uses Math.List.flatten
Elixir.List.flatten      #=> uses List.flatten
Elixir.Math.List.flatten #=> uses Math.List.flatten

Elixir中定義的所有模組都在一個主Elixir名稱空間。但是為方便起見,我們平時都不再前面加‘Elixir’。

別名常被使用與定義快捷方式中。實際上不帶as選項去呼叫alias會自動將這個別名設定為模組名的最後一部分:

alias Math.List

就相當於:

alias Math.List, as: List

注意,別名是詞法作用域。即,允許你在某個函式中設定別名:

defmodule Math do
  def plus(a, b) do
   alias Math.List
    # ...
  end

  def minus(a, b) do
    # ...
  end
end

上面例子中,alias指令只在函式plus/2中有用,minus/2不受影響。

13.2-require

Elixir提供了許多巨集,用於超程式設計(寫能生成程式碼的程式碼)。

巨集就是一堆程式碼,只是它是在編譯時被展開和執行。就是說,為了使用一個巨集,你需要確保它的模組和實現在編譯時可用。 這使用require指令:

iex> Integer.odd?(3)
** (CompileError) iex:1: you must require Integer before invoking the macro Integer.odd?/1
iex> require Integer
nil
iex> Integer.odd?(3)
true

Elixir中,Integer.odd?/1函式被定義為一個巨集,因此他可以被當作衛兵表示式(guards)使用。 為了呼叫這個巨集,你首先得確保用require引用了Integer模組。

總的來說,一個模組在被用到之前不需要被require引用,除非我們想讓這個巨集在整個模組中可用。 嘗試呼叫一個沒有引入的巨集會導致報錯。注意,像alias指令一樣,require也是詞法作用域的。 下文中我們會進一步討論巨集。

13.3-import

當我們想輕鬆地訪問別的模組中的函式和巨集時,我們使用import指令加上那個模組完整的名字。 例如,如果我們想多次使用List模組中的duplicate函式,我們可以簡單地import它:

iex> import List, only: [duplicate: 2]
nil
iex> duplicate :ok, 3
[:ok, :ok, :ok]

這個例子中,我們只從List模組匯入了函式duplicate/2。儘管only:選項是可選的,但是推薦使用。 除了only:選項,還有except:選項。
使用選項only:還可以傳遞給它:macros:functions。如下面例子,程式僅匯入Integer模組中所有的巨集:

import Integer, only: :macros

或者,僅匯入所有的函式:

import Integer, only: :functions

注意,import也是詞法作用域,意味著我們可以在某特定函式中匯入巨集或方法:

defmodule Math do
  def some_function do
    import List, only: [duplicate: 2]
    # call duplicate
  end
end

在此例子中,匯入的函式List.duplicate/2只在那個函式中可見。這個模組中其它函式都不可用(自然,別的模組也不受影響)。

注意,若import一個模組,將自動require它。

13.4-別名機制

講到這裡你會問,Elixir的別名到底是什麼,它是怎麼實現的?

Elixir中的別名是以大寫字母開頭的識別符號(像String, Keyword等等),在編譯時會被轉換為原子。 例如,別名‘String’會被轉換為:"Elixir.String"

iex> is_atom(String)
true
iex> to_string(String)
"Elixir.String"
iex> :"Elixir.String"
String

使用alias/2指令,其實只是簡單地改變了這個別名將要轉換的結果。

別名如此工作,是因為在Erlang虛擬機器中(以及後來的Elixir),模組是被表述成原子的。例如,我們呼叫一個Erlang模組的機制是:

iex> :lists.flatten([1,[2],3])
[1, 2, 3]

這也是允許我們動態呼叫某模組內給定函式的機制:

iex> mod = :lists
:lists
iex> mod.flatten([1,[2],3])
[1,2,3]

一句話,我們只是簡單地在原子:lists上呼叫了函式flatten

13.5-巢狀

介紹了別名,現在可以講講巢狀(nesting)以及它在Elixir中是如何工作的。

考慮下面的例子:

defmodule Foo do
  defmodule Bar do
  end
end

該例子定義了兩個模組FooFoo.Bar。後者在Foo中可以用Bar為名來訪問,因為它們在同一個詞法作用域中。 如果之後開發者決定把Bar模組挪到另一個檔案中,那它就需要以全名(Foo.Bar)或者別名來指代。

換句話說,上面的程式碼等同於:

defmodule Elixir.Foo do
  defmodule Elixir.Foo.Bar do
  end
  alias Elixir.Foo.Bar, as: Bar
end

在以後章節我們可以看到,別名在巨集機制中扮演了很重要的角色,來保證巨集是乾淨的(hygienic)。

討論到這裡,模組基本上講得差不多了。之後會講解模組的屬性。

相關文章