美文网首页Elixir 编程Elixir & PhoenixElixir
Elixir 简明笔记(十二) --- 模块

Elixir 简明笔记(十二) --- 模块

作者: 人世间 | 来源:发表于2016-03-29 21:53 被阅读500次

    模块

    与大多数语言一样,Elixir同样也提供了模块的功能。模块能够更好的组织代码。前面我们见识了匿名函数,作为函数式语言,肯定会有命名函数。不同于别的语言,elixir的命名函数必须定义在模块之中。

    模块是一些命名函数的集合,类似命名空间,把不同功能的函数组织在不同的模块里面。模块可以自己定义,也可以通过安装第三方包。当然Elixir提供了一下非常有用的模块,这些称之为标准库。通常这些模块属于内核。例如IO模块,使用模块名.函数名(参数) ModuleName.function_name(args)调用模块的函数:

    iex(1)> IO.puts "Hello World"   # 调用 IO 模块的 puts 方法,用于向终端打印字符串
    Hello World
    :ok                             # puts 函数的返回,作为函数式语言,函数通常都有返回值
    

    模块定义

    定义模块很简单,使用defmodule宏定义即可,对于defmodule可以把其暂时理解为其他语言终端关键字。在模块里面,可以使用 def结构定义函数。新建一个文件夹learn-elixir作为我们应用的根目录,然后编辑文件geometry.ex,例如:

    defmodule Geometry do               # 定义模块
        def rectangle_area(a, b) do    
            a * b
        end
    end
    

    通常模块使用 CamelCase 命名法。即单词的首字母都是大写。

    模块执行

    geometry.ex 文件定义了Geometry模块,Geometry模块定义了rectangle_area方法。下面可以执行模块的函数。可以使用elixir 来执行 geometry.ex。运行 elixir geometry.ex 。并没有反馈。程序被执行了,可是代码仅定义了一个模块,没有输出。因此使用 iex 来执行。

    learn-elixir  iex geometry.ex
    Erlang/OTP 17 [erts-6.4] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
    
    Interactive Elixir (1.0.4) - press Ctrl+C to exit (type h() ENTER for help)
    iex(1)> Ge
    GenEvent     GenServer    Geometry
    iex(1)> Geometry.rectangle_area 2, 5
    10
    iex(2)> ls
    geometry.ex
    :ok
    

    使用 iex 执行ex文件,会把模块上下文导入到iex中,因此可以直接运行Geometry的方法。如果直接运行iex,想要得到模块,必须先编译。编译ex也有两种方式,一种是使用elixirc 命令

    ☁  learn-elixir  elixirc geometry.ex
    ☁  learn-elixir  ls
    Elixir.Geometry.beam geometry.ex
    

    当然目录会生成 .beam 字节码,该文件是运行在Erlang虚拟机上的文件。从Elixir.Geometry.beam 文件也可以看出,所有Elixir模块,其实是挂载在Elixir 这个模块。如果使用iex,也可以编译,先删掉Elixir.Geometry.beam 。然后启动iex:

    ☁  learn-elixir  ls
    Elixir.Geometry.beam geometry.ex
    ☁  learn-elixir  rm -rf Elixir.Geometry.beam
    ☁  learn-elixir  ls
    geometry.ex
    ☁  learn-elixir  iex
    Erlang/OTP 17 [erts-6.4] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
    
    Interactive Elixir (1.0.4) - press Ctrl+C to exit (type h() ENTER for help)
    iex(1)> c "geometry.ex"
    [Geometry]
    iex(2)> ls
    Elixir.Geometry.beam     geometry.ex
    :ok
    iex(3)> Geometry.rectangle_area 1 2
    2
    

    只要是编译后,在编译后的文件目录下启动iex,模块都能导入iex的上下雯。

    模块编译

    我们知道了iex导入模块运行。通常情况下,执行程序不会使用 iex 这种交互式的平台。因此我们定义一个程序执行的入口文件,并在入口文件中应用执行模块函数。新建一个main.ex 文件。

    IO.puts "start main"
    
    ret = Geometry.rectangle_area 1, 2
    
    IO.puts "#{ret}"
    
    IO.puts "end main"
    

    然后运行 main.ex可以看到模块被执行了。

    ☁  learn-elixir  elixir main.ex
    start main
    2
    end main
    ☁  learn-elixir
    

    模块也可以被导入文件中,使用 import 命令,就能将模块中的函数全部导入当前的执行环境中。修改main.ex 如下:

    IO.puts "start main"
    
    import Geometry
    
    ret = rectangle_area 1, 2
    
    IO.puts "#{ret}"
    
    IO.puts "end main"
    

    再次运行同样可以得到修改前的结果。须知,之所以可以导入模块,原因是有了编译的.beam文件,如果干掉这个文件,再执行将会报错:

    ☁  learn-elixir  rm -rf Elixir.Geometry.beam
    ☁  learn-elixir  elixir main.ex
    ** (CompileError) main.ex:3: module Geometry is not loaded and could not be found
        (stdlib) lists.erl:1352: :lists.mapfoldl/3
        (stdlib) lists.erl:1353: :lists.mapfoldl/3
    

    模块嵌套

    elixir的模块也是可以嵌套。并且还提供了一个.的语法糖。修改 geometry.ex 并编译:

    defmodule Geometry do
        defmodule Rectangle do
             def area(a, b) do
                 a * b
             end
        end
        
        def whoIam? do
             IO.puts __MODULE__
        end
    end
    

    编译之后,我们看见了如下四个文件:

    ☁  learn-elixir  ls
    Elixir.Geometry.Rectangle.beam geometry.ex
    Elixir.Geometry.beam           main.ex
    

    可以看到,elixir 编译一共产生了两个模块,一个是Geometry模块,另外一个是Geometry.Rectangle模块。内嵌定义的模块,编译之后,会把全路径给写出来。也就是外套的模块也会被加入。再修改 main.ex ,尝试使用这两个模块。

     import Geometry
    
    # 调用 Geometry 的 whoIam?方法
     whoIam?
    
     ret = Rectangle.area 1, 2
    
     IO.puts "#{ret}"
    
    

    运行main之后,输出

    ☁  learn-elixir  elixir main.ex
    Elixir.Geometry
    ** (UndefinedFunctionError) undefined function: Rectangle.area/2 (module Rectangle is not available)
        Rectangle.area(1, 2)
        main.ex:6: (file)
        (elixir) lib/code.ex:307: Code.require_file/2
    

    出错了,看起来是Rectangle这个模块不存在,前面我们把Rectangle嵌套了再Geometry模块中,为什么导入Geometry之后不能接着使用 Rectangle?再次修改main.ex。

     import Geometry
     import Geometry.Rectangle
    
    
     whoIam?
    
     ret = area 1, 2
    
     IO.puts "#{ret}"
    

    此时可以得到正确的结果。由此可见,所谓的嵌套结果,并没有命名空间的概念。对于elixir,模块的嵌套只是代码组织的一种手段,再编译后。只存在 Geometry 模块和 Geometry.Rectangle 模块,并不存在 Rectangle 模块。这一点从编译后的文件可以看得出来。因此,为了嵌套模块,elixir提供了.这样的操作符。上述的内嵌代码可以修改如下:

      defmodule Geometry do
          def whoIam? do
              IO.puts __MODULE__
          end
      end
     
      defmodule Geometry.Rectangle do
          def area(a, b) do
              a * b
          end
     
      end
    
    

    使用点.来代替嵌套,编译再运行main.ex。同样得到了正确的结果。再一次强调,无论是.还是嵌套模块,他们只是用来组织代码,一旦编译后,Geometry 和 Geometry.Rectangle是两个完全不同的模块,并且他们之间没有任何联系。

    基于上面对模块的认识,既然嵌套模块再编译后都仅和模块名有关,因此可以把不同的模块放到一个文件夹,通过文件夹来组织模块。

    新建文件夹,geometry 和 rectangle ,目录结构如下:

    ├── geometry
    │   ├── geometry.ex
    │   └── rectangle
    │       └── rectangle.ex
    └── main.ex
    

    然后修改 geometry/geometry.ex 如下:

    defmodule Geometry do
        def whoIam? do
            IO.puts __MODULE__
        end
    end
    

    geometry/rectangle/rectangle.ex

    defmodule Geometry.Rectangle do
        def area(a, b) do
            a * b
        end
    end
    
    

    然后分别编译这两个文件结果如下:

    ☁  learn-elixir  ls
    geometry main.ex
    ☁  learn-elixir  elixirc geometry/geometry.ex
    ☁  learn-elixir  ls
    Elixir.Geometry.beam geometry             main.ex
    ☁  learn-elixir  elixirc geometry/rectangle/rectangle.ex
    ☁  learn-elixir  ls
    Elixir.Geometry.Rectangle.beam geometry
    Elixir.Geometry.beam           main.ex
    ☁  learn-elixir  elixir main.ex
    Elixir.Geometry
    2
    

    尽管两个模块文件都被文件夹组织在一起了,但是最后编译后的文件,还是再编译的当前目录下。由此可见,在模块中编写代码的时候,模块间相互引用,也变得很方便,因为都是相对根目录而言的导入。

    再次修改 geometry.ex ,我们需要测试再 geometry.ex 使用 Geometry.Rectangle模块。

    defmodule Geometry do
        def whoIam? do
            IO.puts __MODULE__
        end
    
        import Geometry.Rectangle
    
        def call_rectangle_area(a, b) do
            area(a, b)
        end
    end
    

    然后修改 main.ex

    import Geometry
    
    ret = call_rectangle_area(1, 2)
    
    IO.puts "#{ret}"
    
    

    因为修改了文件,需要对 geometry.ex 重新编译,编译之后运行 main.ex

    ☁  learn-elixir  elixirc geometry/geometry.ex
    geometry/geometry.ex:2: warning: redefining module Geometry
    ☁  learn-elixir  elixir main.ex
    2
    

    总结

    综上所述,对于elixir模块的探索大致可以归结为模块名作为模块的区分,. 或者嵌套模块只是代码组织的一种形式,在编译后,并没有相关的联系。这从编译后的.beam文件可以看出来。

    在不同模块中调用其他模块的函数,需要先把模块导入。当然关于导入模块,elixir提供了 import,require, use, 以及 alias等方式。它们到底有什么差别呢?下一篇笔记再来深究。

    相关文章

      网友评论

        本文标题:Elixir 简明笔记(十二) --- 模块

        本文链接:https://www.haomeiwen.com/subject/rsmslttx.html