结构
在第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