Elixir入门教程-结构体

  1. 定义结构体
  2. 访问和修改结构体
  3. 结构体的底层是裸映射
  4. 默认值和所需的键

在第七章我们学了映射:

1
2
3
4
5
6
iex> map = %{a: 1, b: 2}
%{a: 1, b: 2}
iex> map[:a]
1
iex> %{map | a: 3}
%{a: 3, b: 2}

结构体是构建于映射之上的扩展,它提供了编译时检查和默认值。

定义结构体

要定义结构体,就要使用 defstruct 结构

1
2
3
iex> defmodule User do
...> defstruct name: "John", age: 27
...> end

defstruct 用关键字列表定义结构体有什么字段以及它们的默认值。

结构体的名字就是它们被定义所在的模块的名字。上面例子中,我们定义了一个名字为User的结构体。

我们现在可以用与创建映射相似的语法来创建 User 结构体:

1
2
3
4
iex> %User{}
%User{age: 27, name: "John"}
iex> %User{name: "Meg"}
%User{age: 27, name: "Meg"}

结构体提供编译时检查来保证只有通过 defstruct 定义的字段(包括所有被定义的字段)才被允许存在它里面:

1
2
iex> %User{oops: :field}
** (KeyError) key :oops not found in: %User{age: 27, name: "John"}

访问和修改结构体

当我们讨论映射的时候,我们展示了我们如何访问和修改映射的字段。同样的技术(和同样的语法)也应用在结构体:

1
2
3
4
5
6
7
8
iex> john = %User{}
%User{age: 27, name: "John"}
iex> john.name
"John"
iex> meg = %{john | name: "Meg"}
%User{age: 27, name: "Meg"}
iex> %{meg | oops: :field}
** (KeyError) key :oops not found in: %User{age: 27, name: "Meg"}

当使用修改语法(|)的时候,Erlang虚拟机意识到没有新的键将加入结构体,准许底层的映射在内存里共享它们的结构。上述例子里,john 和 meg 共享内存里相同的键结构。

结构也可以用于模式匹配,既为匹配上的特定键的值以及确保匹配的值是一个同一类型的结构的匹配值。

1
2
3
4
5
6
iex> %User{name: name} = john
%User{age: 27, name: "John"}
iex> name
"John"
iex> %User{} = %{}
** (MatchError) no match of right hand side value: %{}

结构体的底层是裸映射

上面的例子里,模式匹配可以运作是因为结构体的底层是有固定字段集合的裸映射。作为映射,结构体存储了一个名字为 __struct__ 的“特别的”字段,它持有结构体的名字:

1
2
3
4
iex> is_map(john)
true
iex> john.__struct__
User

注意:我们称结构体为裸映射是因为为映射实现的协议没有任何一个可用于结构体。例如,你既不能枚举也不能访问一个结构体:

1
2
3
4
5
6
7
iex> john = %User{}
%User{age: 27, name: "John"}
iex> john[:name]
** (UndefinedFunctionError) function User.fetch/2 is undefined (User does not implement the Access behaviour)
User.fetch(%User{age: 27, name: "John"}, :name)
iex> Enum.each john, fn({field, value}) -> IO.puts(value) end
** (Protocol.UndefinedError) protocol Enumerable not implemented for %User{age: 27, name: "John"}

然后,因为结构体就是映射,它们可以应用于Map模块的函数:

1
2
3
4
5
6
iex> kurt = Map.put(%User{}, :name, "Kurt")
%User{age: 27, name: "Kurt"}
iex> Map.merge(kurt, %User{name: "Takashi"})
%User{age: 27, name: "Takashi"}
iex> Map.keys(john)
[:__struct__, :age, :name]

结构和协议为Elixir开发者提供了最重要的特征之一:数据多态性。这是我们下一章将要探索的。

默认值和所需的键

如果在定义结构体的时候你没有指定一个键的默认值,则nil将被当做它的默认值:

1
2
3
4
5
iex> defmodule Product do
...> defstruct [:name]
...> end
iex> %Product{}
%Product{name: nil}

你也可以强制在创建结构体的时候某些键必需被指定:

1
2
3
4
5
6
7
iex> defmodule Car do
...> @enforce_keys [:make]
...> defstruct [:model, :make]
...> end
iex> %Car{}
** (ArgumentError) the following keys must also be given when building struct Car: [:make]
expanding struct: Car.__struct__/1

原文链接: http://elixir-lang.org/getting-started/structs.html