结构 | Structs
结构
在第7章中,我们了解了地图:
iex> map = %{a: 1, b: 2}
%{a: 1, b: 2}
iex> map[:a]
1
iex> %{map | a: 3}
%{a: 3, b: 2}
结构是构建在提供编译时检查和默认值的映射之上的扩展。
定义结构
要定义一个结构,使用defstruct
结构:
iex> defmodule User do
...> defstruct name: "John", age: 27
...> end
用于defstruct
定义结构将具有哪些字段以及其默认值的关键字列表。
结构体采用它们定义的模块的名称。在上面的例子中,我们定义了一个名为 struct 的结构体User
。
我们现在可以通过使用类似于用于创建地图的语法来创建User
结构:
iex> %User{}
%User{age: 27, name: "John"}
iex> %User{name: "Meg"}
%User{age: 27, name: "Meg"}
结构提供了编译时
保证,只有通过定义的字段(以及所有
字段)defstruct
才会被允许存在于结构中:
iex> %User{oops: :field}
** (KeyError) key :oops not found in: %User{age: 27, name: "John"}
访问和更新结构
当我们讨论地图时,我们展示了我们如何访问和更新地图的字段。相同的技术(以及相同的语法)也适用于结构:
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"}
当使用更新语法(|
)时,虚拟机知道没有新的键将被添加到结构中,允许下面的地图在内存中共享它们的结构。在上面的例子中,两个john
和meg
共享存储器中的相同的密钥的结构。
结构体也可用于模式匹配,既用于匹配特定键的值,又用于确保匹配值是与匹配值相同类型的结构体。
iex> %User{name: name} = john
%User{age: 27, name: "John"}
iex> name
"John"
iex> %User{} = %{}
** (MatchError) no match of right hand side value: %{}
结构是底下裸露的地图
在上面的例子中,模式匹配是有效的,因为在结构下面的是裸露的地图和一组固定的字段。作为地图,结构存储一个名为“special”的字段__struct__
,其中包含结构体的名称:
iex> is_map(john)
true
iex> john.__struct__
User
请注意,我们将结构称为裸
地图,因为没有为地图实现的协议可用于结构。例如,你既不能枚举也不能访问一个结构体:
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
模块中的功能一起使用:
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
则将假定:
iex> defmodule Product do
...> defstruct [:name]
...> end
iex> %Product{}
%Product{name: nil}
您还可以强制在创建结构时必须指定某些键:
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