美文网首页
Elixir in Action Note 2 Building

Elixir in Action Note 2 Building

作者: c8ac4dad76db | 来源:发表于2018-08-12 17:37 被阅读13次

本章涵盖:

  • 终端交互
  • 变量
  • 组织代码
  • 理解类型系统
  • 操作符
  • 理解运行时

本章讲述基本的模块、函数和类型系统。

开始前,确保Elixir版本为1.6以上、Erlang版本为20以上。可以参考官方文档安装Erlang和Elixir,也可以使用 asdf-vm/asdf 来安装。

With that out of the way, let’s start the tour of Elixir. The first thing you should know about is the interactive shell.

更多参考

2.1 终端交互

在终端中输入命令 iex:

$ iex
Erlang/OTP 20 [erts-9.1] [source] [64-bit] [smp:8:8] [ds:8:8:10]
  [async-threads:10] [hipe] [kernel-poll:false]

Interactive Elixir (1.6.0) - press Ctrl+C to exit
  (type h() ENTER for help)

iex(1)>

接着可以运行Elixir表达式:

iex(1)> 1 + 2
3

NOTE
Elixir中的一切都是表达式返回的值。不仅仅包含函数,也包含类似 ifcase 的结构。

最快的退出方式是按两次 Ctrl + C,如果想用更优雅的方式退出,可以调用 System.halt

可以在终端做很多事情,可以键入 h 获取基本的帮助:

iex(4)> h

可以查看某个模块的文档:

iex(5)> h IEx

可以在线查阅同样的文档: https://hexdocs.pm/iex

接下来,我们开始理解变量。

2.2 变量

iex(1)> monthly_salary = 10000
10000
iex(2)> monthly_salary
10000
iex(3)> monthly_salary * 12
120000

2.3 组织代码

2.3.1 模块

模块是函数的集合,类似命名空间。每一个Elixir函数必须被定义在模块中。

Elixir标准库提供了很多有用的模块。比如 IO 模块能用于 I/O 操作。IO中的puts函数用于在屏幕上打印消息。

iex(1)> IO.puts("Hello World!")
Hello World!
:ok

调用模块终的函数,语法是这样 ModuleName.function_name(args)

使用 defmodule 定义模块。在模块中,使用 def 定义函数。

defmodule Geometry do
  def rectangle_area(a, b) do
    a * b
  end
end

将以上代码保持为 geometry.ex 文件:

$ iex geometry.ex

iex(1)> Geometry.rectangle_area(6, 7)
42

很简单! 我们创建了一个 Geometry 模块,加载到终端会话,并用于计算矩形的面积。

源文件中可以包含多个模块:

defmodule Module1 do
  ...
end

defmodule Module2 do
  ...
end

模块名称必须遵循一定的规则。它始于一个大写字母,通常为驼峰风格。模块名称可以包含 # ,_, . 字符。后者常用于组织模块层级:

defmodule Geometry.Rectangle do
  ...
end

defmodule Geometry.Circle do
  ...
end

嵌套模块:

defmodule Geometry do
  defmodule Rectangle do
    ...
  end
  ...
end

2.3.2 函数

函数通常以小写字母或者下划线开头,几个单词之间可以用下划线连接。

函数名可以用 ?! 结束,? 通常表示该函数返回布尔型的 truefalse! 表示函数会运行时出错。

defmodule Geometry do
  def rectangle_area(a, b) do
    ...
  end
end

不带参数的函数可以 省略括号:

defmodule Program do
  def run do
    ...
  end
end

将函数放在一行的写法:

defmodule Geometry do
  def rectangle_area(a, b), do: a * b
end

运行函数:

$ iex geometry.ex

iex(1)> Geometry.rectangle_area(3, 2)
6

当然,我们也可以将函数返回的结果存储到变量:

iex(2)> area = Geometry.rectangle_area(3, 2)
6

iex(3)> area
6

在Elixir中,括号也是可选的,但并不推荐这样做:

iex(4)> Geometry.rectangle_area 3, 2
6

如果函数在相同的模块,调用的时候可以省略模块名:

defmodule Geometry do
  def rectangle_area(a, b) do
    a * b
  end

  def square_area(a) do
    rectangle_area(a, a)
  end
end

Elixir内置的管道操作符 |> ,可以很方便的使用:

iex(5)> -5 |> abs() |> Integer.to_string() |> IO.puts()
5

意思等价于:

iex(6)> IO.puts(Integer.to_string(abs(-5)))
5

以下两个表达是等价的:

prev(arg1, arg2) |> next(arg3, arg4)
next(prev(arg1, arg2), arg3, arg4)

增加管道操作符的可读性:

-5
|> abs()
|> Integer.to_string()
|> IO.puts()

2.3.3 函数参数

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

上面的函数有两个参数,在Elixir中通常称为 Rectangle.area/2/2 表示参数数量。为何强调这一点,因为函数名相同的函数,但是参数数量不同,就表示不同的函数:

defmodule Rectangle do
  def area(a), do: area(a, a)

  def area(a, b), do: a * b
end

默认参数:

defmodule Calculator do
  def sum(a, b \\ 0) do
    a + b
  end
end
defmodule MyModule do
  def fun(a, b \\ 1, c, d \\ 2) do
    a + b + c + d
  end
end

2.3.4 函数可见性

defmodule TestPrivate do
  def double(a) do
    sum(a, a)
  end

  defp sum(a, b) do
    a + b
  end
end
iex(1)> TestPrivate.double(3)
6

iex(2)> TestPrivate.sum(3, 4)
** (UndefinedFunctionError) function TestPrivate.sum/2
...

2.3.5 导入和别名

defmodule MyModule do
  import IO

  def my_function do
    puts "Calling imported function."
  end
end
defmodule MyModule do
  alias IO, as: MyIO

  def my_function do
    MyIO.puts("Calling imported function.")
  end
end
defmodule MyModule do
  alias Geometry.Rectangle, as: Rectangle

  def my_function do
    Rectangle.area(...)
  end
end
defmodule MyModule do
  alias Geometry.Rectangle

  def my_function do
    Rectangle.area(...)
  end
end

别名可以减少混淆,特别是要从名字很长的模块中调用函数。

2.3.6 模块属性

iex(1)> defmodule Circle do
          @pi 3.14159

          def area(r), do: r*r*@pi
          def circumference(r), do: 2*r*@pi
        end

iex(2)> Circle.area(1)
3.14159

iex(3)> Circle.circumference(1)
6.28318

默认模块属性 @moduledoc@doc

defmodule Circle do
  @moduledoc "Implements basic circle functions"
  @pi 3.14159

  @doc "Computes the area of a circle"
  def area(r), do: r*r*@pi

  @doc "Computes the circumference of a circle"
  def circumference(r), do: 2*r*@pi
end
iex(1)> Code.get_docs(Circle, :moduledoc)
{1, "Implements basic circle functions"}
iex(2)> h Circle
                                     Circle
Implements basic circle functions

iex(3)> h Circle.area
                                  def area(r)
Computes the area of a circle

Type specifications

defmodule Circle do
  @pi 3.14159

  @spec area(number) :: number
  def area(r), do: r*r*@pi

  @spec circumference(number) :: number
  def circumference(r), do: 2*r*@pi
end

2.3.7 注释

# This is a comment
a = 3.14      # so is this

2.4 了解系统类型

2.4.1 Number

整数、浮点数

iex(1)> 3
3

iex(2)> 0xFF
255

iex(3)> 3.14
3.14

iex(4)> 1.0e-2
0.01

支持标准的计算符

iex(5)> 1 + 2 * 3
7
iex(6)> 4/2
2.0

iex(7)> 3/2
1.5
iex(8)> div(5,2)
2

iex(9)> rem(5,2)
1
iex(10)> 1_000_000
1000000

iex(11)> 999999999999999999999999999999999999999999999999999999999999
999999999999999999999999999999999999999999999999999999999999

2.4.2 Atom

:an_atom
:another_atom

:"an atom with spaces"

variable = :some_atom

别名
省略冒号,以大写开头:

AnAtom

:"Elixir.AnAtom":
iex(1)> AnAtom == :"Elixir.AnAtom"
true

iex(2)> AnAtom == Elixir.AnAtom
true

iex(3)> alias IO, as: MyIO

iex(4)> MyIO.puts("Hello!")
Hello!

iex(5)> MyIO == Elixir.IO
true

Atoms as booleans

iex(1)> :true == true
true

iex(2)> :false == false
true

iex(1)> true and false
false

iex(2)> false or true
true

iex(3)> not false
true

iex(4)> not :an_atom_other_than_true_or_false
** (ArgumentError) argument error

Nil and truthy values

iex(1)> nil == :nil
true

iex(1)> nil || false || 5 || true
5

iex(1)> true && 5
5

iex(2)> false && 5
false

iex(3)> nil && 5
nil
read_cached || read_from_disk || read_from_database

database_value = connection_established? && read_data

2.4.3 Tuples

iex(1)> person = {"Bob", 25}
{"Bob", 25}

iex(2)> age = elem(person, 1)
25

iex(3)> put_elem(person, 1, 26)
{"Bob", 26}

iex(4)> person
{"Bob", 25}

iex(5)> older_person = put_elem(person, 1, 26)
{"Bob", 26}

iex(6)> older_person
{"Bob", 26}

iex(7)> person = put_elem(person, 1, 26)
{"Bob", 26}

2.4.4 Lists

iex(1)> prime_numbers = [2, 3, 5, 7]
[2, 3, 5, 7]

iex(2)> length(prime_numbers)
4

iex(3)> Enum.at(prime_numbers, 3)
7

iex(4)> 5 in prime_numbers
true

iex(5)> 4 in prime_numbers
false

iex(6)> List.replace_at(prime_numbers, 0, 11)
[11, 3, 5, 7]

iex(7)> new_primes = List.replace_at(prime_numbers, 0, 11)
[11, 3, 5, 7]

iex(8)> prime_numbers = List.replace_at(prime_numbers, 0, 11)
[11, 3, 5, 7]

iex(9)> List.insert_at(prime_numbers, 3, 13)
[11, 3, 5, 13, 7]

iex(10)> List.insert_at(prime_numbers, -1, 13)
[11, 3, 5, 7, 13]

iex(11)> [1, 2, 3] ++ [4, 5]
[1, 2, 3, 4, 5]

Recursive list definition

a_list = [head | tail]
iex(1)> [1 | []]
[1]

iex(2)> [1 | [2 | []]]
[1, 2]

iex(3)> [1 | [2]]
[1, 2]

iex(4)> [1 | [2, 3, 4]]
[1, 2, 3, 4]

iex(1)> [1 | [2 | [3 | [4 | []]]]]
[1, 2, 3, 4]

iex(1)> hd([1, 2, 3, 4])
1

iex(2)> tl([1, 2, 3, 4])
[2, 3, 4]

iex(1)> a_list = [5, :value, true]
[5, :value, true]

iex(2)> new_list = [:new_element | a_list]
[:new_element, 5, :value, true]

2.4.5 不可变性 Immutability

a_tuple = {a, b, c}
new_tuple = put_elem(a_tuple, 1, b2)

修改列表


2.4.6 Maps

Dynamically sized maps

iex(1)> empty_map = %{}

iex(2)> squares = %{1 => 1, 2 => 4, 3 => 9}

iex(3)> squares = Map.new([{1, 1}, {2, 4}, {3, 9}])
%{1 => 1, 2 => 4, 3 => 9}

iex(4)> squares[2]
4

iex(5)> squares[4]
nil

iex(6)> Map.get(squares, 2)
4

iex(7)> Map.get(squares, 4)
nil

iex(8)> Map.get(squares, 4, :not_found)
:not_found

iex(9)> Map.fetch(squares, 2)
{:ok, 4}

iex(10)> Map.fetch(squares, 4)
:error

iex(11)> Map.fetch!(squares, 2)
4

iex(12)> Map.fetch!(squares, 4)
** (KeyError) key 4 not found in: %{1 => 1, 2 => 4, 3 => 9}
    (stdlib) :maps.get(4, %{1 => 1, 2 => 4, 3 => 9})

iex(13)> squares = Map.put(squares, 4, 16)
%{1 => 1, 2 => 4, 3 => 9, 4 => 16}

iex(14)> squares[4]
16

Structured data

bob = %{:name => "Bob", :age => 25, :works_at => "Initech"}

iex(2)> bob = %{name: "Bob", age: 25, works_at: "Initech"}

iex(3)> bob[:works_at]
"Initech"

iex(4)> bob[:non_existent_field]
nil

iex(5)> bob.age
25

iex(6)> bob.non_existent_field
** (KeyError) key :non_existent_field not found

iex(7)> next_years_bob = %{bob | age: 26}
%{age: 26, name: "Bob", works_at: "Initech"}

iex(8)> %{bob | age: 26, works_at: "Initrode"}
%{age: 26, name: "Bob", works_at: "Initrode"}

iex(9)> %{bob | works_in: "Initech"}
** (KeyError) key :works_in not found

2.4.7 Binaries and bitstrings

iex(1)> <<1, 2, 3>>
<<1, 2, 3>>

iex(2)> <\<256>>
<\<0>>

iex(3)> <\<257>>
<\<1>>

iex(4)> <\<512>>
<\<0>>

iex(5)> <<257::16>>
<<1, 1>>

iex(6)> <<1::4, 15::4>>
<\<31>>

iex(7)> <<1::1, 0::1, 1::1>>
<<5::size(3)>>

iex(8)> <<1, 2>> <> <<3, 4>>
<<1, 2, 3, 4>>

2.4.8 Strings

Binary strings

iex(1)> "This is a string"
"This is a string"

iex(2)> "Embedded expression: #{3 + 0.14}"
"Embedded expression: 3.14"

iex(3)> "\r \n \" \\"

iex(4)> "
        This is
        a multiline string
        "

iex(5)> ~s(This is also a string)
"This is also a string"

iex(6)> ~s("Do... or do not. There is no try." -Master Yoda)
"\"Do... or do not. There is no try.\" -Master Yoda"

iex(7)> ~S(Not interpolated #{3 + 0.14})
"Not interpolated \#{3 + 0.14}"

iex(8)> ~S(Not escaped \n)
"Not escaped \\n"

iex(9)> """
        Heredoc must end on its own line """
        """
"Heredoc must end on its own line \"\"\"\n"

iex(10)> "String" <> " " <> "concatenation"
"String concatenation"

Character lists

iex(1)> 'ABC'
'ABC'

iex(2)> [65, 66, 67]
'ABC'

iex(3)> 'Interpolation: #{3 + 0.14}'
'Interpolation: 3.14'

iex(4)> ~c(Character list sigil)
'Character list sigil'

iex(5)> ~C(Unescaped sigil #{3 + 0.14})
'Unescaped sigil \#{3 + 0.14}'

iex(6)> '''
        Heredoc
        '''
'Heredoc\n'

iex(7)> String.to_charlist("ABC")
'ABC'

2.4.9 第一个类函数

iex(1)> square = fn x ->
          x * x
        end
iex(2)> square.(5)
25
iex(3)> print_element = fn x -> IO.puts(x) end
iex(4)> Enum.each(
          [1, 2, 3],
          print_element
        )
1
2
3

:ok
iex(5)> Enum.each(
          [1, 2, 3],
          fn x -> IO.puts(x) end
        )
1
2
3
iex(6)> Enum.each(
          [1, 2, 3],
          &IO.puts/1
        )
iex(7)> lambda = fn x, y, z -> x * y + z end
iex(8)> lambda = &(&1 * &2 + &3)
iex(9)> lambda.(2, 3, 4)
10

Closures

iex(1)> outside_var = 5
5
iex(2)> my_lambda = fn ->
          IO.puts(outside_var)
        end

iex(3)> my_lambda.()
5
iex(1)> outside_var = 5
iex(2)> lambda = fn -> IO.puts(outside_var) end
iex(3)> outside_var = 6
iex(4)> lambda.()
5

2.4.10 其他内建类型

有几种类型我还没有呈现。我们不会深入讨论它们,但为了完整起见,值得一提:

  • Reference
  • pid
  • port identifier

2.4.11 Higher-level types

Range

iex(1)> range = 1..2

iex(2)> 2 in range
true

iex(3)> -1 in range
false

iex(4)> Enum.each(
          1..3,
          &IO.puts/1
        )

1
2
3

Keyword lists

iex(1)> days = [{:monday, 1}, {:tuesday, 2}, {:wednesday, 3}]

iex(3)> Keyword.get(days, :monday)
1

iex(4)> Keyword.get(days, :noday)
nil

iex(5)> days[:tuesday]
2

iex(6)> IO.inspect([100, 200, 300])
[100, 200, 300]

iex(7)> IO.inspect([100, 200, 300], [width: 3])
[100,
 200,
 300]

iex(8)> IO.inspect([100, 200, 300], width: 3, limit: 1)
[100,
 ...]
def my_fun(arg1, arg2, opts \\ []) do
  ...
end

MapSet

iex(1)> days = MapSet.new([:monday, :tuesday, :wednesday])
#MapSet<[:monday, :tuesday, :wednesday]>

iex(2)> MapSet.member?(days, :monday)
true

iex(3)> MapSet.member?(days, :noday)
false

iex(4)> days = MapSet.put(days, :thursday)
#MapSet<[:monday, :thursday, :tuesday, :wednesday]>

iex(5)> Enum.each(days, &IO.puts/1)
monday
thursday
tuesday
wednesday

Times and dates

iex(1)> date = ~D[2018-01-31]
~D[2018-01-31]

iex(2)> date.year
2018

iex(3)> date.month
1

iex(1)> time = ~T[11:59:12.00007]

iex(2)> time.hour
11

iex(3)> time.minute
59

iex(1)> naive_datetime = ~N[2018-01-31 11:59:12.000007]

iex(2)> naive_datetime.year
2018

iex(3)> naive_datetime.hour
11

iex(4)> datetime = DateTime.from_naive!(naive_datetime, "Etc/UTC")

iex(5)> datetime.year
2018

iex(6)> datetime.hour
11

iex(7)> datetime.time_zone
"Etc/UTC"

2.4.12 IO lists

iex(1)> iolist = [[['H', 'e'], "llo,"], " worl", "d!"]

iex(2)> IO.puts(iolist)
Hello, world!

iex(3)> iolist = []
        iolist = [iolist, "This"]
        iolist = [iolist, " is"]
        iolist = [iolist, " an"]
        iolist = [iolist, " IO list."]

[[[[[], "This"], " is"], " an"], " IO list."]

iex(4)> IO.puts(iolist)
This is an IO list.

2.5 Operators

iex(1)> 1 == 1.0
true

iex(2)> 1 === 1.0
false

2.6 Macros

unless some_expression do
  block_1
else
  block_2
end

if some_expression do
  block_2
else
  block_1
end

2.7 了解运行时

相关文章

网友评论

      本文标题:Elixir in Action Note 2 Building

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