Elixir: use, import, 和 require

概述

use Module除了在Module上调用__using__宏外, 不作其余任何事情. import Module 会把Module模块的全部非私有函数和宏引入到当前模块中, 能够直接经过名字调用函数和宏. require Module 容许使用一个模块的宏, 但并不导入他们. 必须经过全名(带名称空间)来引用.shell

例如:函数

defmodule User do
  defmacro __using__(_opts) do
    IO.puts "use User"
  end

  def get_username() do
    "developerworks"
  end
end
defmodule Hello do
  use User

  def hi() do
    IO.puts "Hello, #{get_username()}"     # User模块未被导入,该函数不存在
  end
end

再来看下面的例子:测试

defmodule User do
  defmacro __using__(_opts) do
    quote do          
      import User     
    end               
  end

  def get_username() do
    "developerworks"
  end
end

defmodule ModB do
  use User

  def hi() do
    IO.puts "Hello, #{get_username()}"     # User模块已导入, 该函数存在
  end
end

当你使用use User的时候, 它生产了一个import语句, 把User的函数和宏插入到Hello中.ui

一个实际的例子

下面是一个实际的例子, 一个Zlib模块用于压缩和解压数据, 代码能够保存为zlib.exs脚本文件方便测试code

defmodule Zlib do

  @moduledoc """
  Zlib compress and decompress functions
  """

  @doc """
  use Zlib
  """
  defmacro __using__(_opts) do
    quote do
      import Zlib
    end
  end

  def compress(data) do
    z = :zlib.open()
    :zlib.deflateInit(z)
    :zlib.deflate(z, data)
    result = :zlib.deflate(z, << >>, :finish)
    :zlib.deflateEnd(z)
    :erlang.list_to_binary(result)
  end

  def decompress(data, wbits \\ 15) do
    z = :zlib.open()
    :zlib.inflateInit(z, wbits)
    [result|_] = :zlib.inflate(z, data)
    :zlib.inflateEnd(z)
    result
  end

end

require Logger
use Zlib

compressed_data = Zlib.compress("test")
Logger.info "#{inspect compressed_data}"
decompressed_data = Zlib.decompress(compressed_data)

Logger.info "#{decompressed_data}"

正确的方式

上面的代码实际上运行的时候回出错, 经过个人测试use Zlib只能用在模块内部. 不能直接在全局空间中使用, 正确的调用代码以下:作用域

defmodule Test do
  @moduledoc """
  压缩和解压缩测试模块.
  """
  def run() do
    require Logger
    # 把Zlib模块中的函数和宏导入到当前模块的做用域中.
    use Zlib
    compressed_data = compress("test")
    Logger.info "#{inspect compressed_data}"
    decompressed_data = decompress(compressed_data)
    Logger.info "#{decompressed_data}"
  end
end

Test.run

经过上述实验, 咱们在模块Zlib中定义了一个 __using__ 宏, 正如文章最开始所述, use Zlib惟一的做用就是调用目标模块Zlib中的__using__宏, 在__using__宏中咱们import了自身, 也就是导入了Zlib模块中的全部公共(定义为def而非defp)的函数和宏.get

能够实验一下, 若是你在Zlib模块中增长一个私有函数(private_test), 那么咱们use Zlib后在模块外部调用private_test是会发生以下错误的.it

zlib.exs:33: warning: function private_test/0 is unused
** (CompileError) zlib.exs:53: undefined function private_test/0
    (stdlib) lists.erl:1337: :lists.foreach/2
    zlib.exs:41: (file)
    (elixir) lib/code.ex:363: Code.require_file/2

另外对于import, 还有only, except选项, 分别表示只导入指定的函数, 以及排除指定的函数, 例如:io

import Zlib, only: compress
import Zlib, except: decompress

有的模块中包含多个同名, 参数数(arity)不一样的函数, 你甚至能够按函数的参数个数来指定要导入的特定函数, 好比, 在Elixir内置的模块List中存在List.to_integer/1List.to_integer/2两个名为to_integer的函数, 若是你只想导入to_integer/1,那么能够按下面的方式来导入:编译

import List, only: [to_integer: 1]

这里与上面的区别在于only接受的是一个列表, 也就是说, 你还能够导入其余你须要指定的函数, 好比下面的代码仅导入了to_integer/1duplicate/2两个List模块中的函数.

import List, only: [to_integer: 1, duplicate: 2]

最后

最后的require比较简单, 其做用相似于C语言中的#include "zlib.h"把头文件包含进来, 编译连接的时候就会去查找对应的共享库. require的做用与之相似,要使用其余模块所提供的函数和宏, 就必需要require进来.

相关文章
相关标签/搜索