15 構造体 - Structs

以前の章でマップについて学びました:

In earlier chapters, we learned about maps:

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

構造体は,初期値,コンパイル時の保証および多態をElixirへもたらす,マップを利用した拡張です.

Structs are extensions on top of maps that bring default values, compile-time guarantees and polymorphism into Elixir.

構造体を定義するには,モジュールの中で単にdefstruct/1を呼びます:

To define a struct, we just need to call defstruct/1 inside a module:

iex> defmodule User do
...>   defstruct name: "john", age: 27
...> end
{:module, User,
 <<70, 79, 82, ...>>, {:__struct__, 0}}

すると%User{}構文を使って構造体の"インスタンス"が作れるようになります:

We can now create "instances" of this struct by using the %User{} syntax:

iex> %User{}
%User{age: 27, name: "john"}
iex> %User{name: "meg"}
%User{age: 27, name: "meg"}
iex> is_map(%User{})
true

構造体は,用意しているフィールドが存在することをコンパイル時に保証します.

Structs give compile-time guarantees that the provided fields exist in the struct:

iex> %User{oops: :field}
** (CompileError) iex:3: unknown key :oops for struct User

マップについて話したとき,既にあるマップのフィールドに対してアクセスやアップデートをできることを示しました.同じことを構造体にもやってみましょう:

When discussing maps, we demonstrated how we can access and update existing fields of a map. The same applies to structs:

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}
** (ArgumentError) argument error

アップデートの構文を使うと,VMはマップ/構造体へ新しいキーが何も追加されないことを認識し,マップがメモリ内で構造を共有できるようにします.上の例だと,johnmegはどちらも同じキーを共有しています.

By using the update syntax, the VM is aware no new keys will be added to the map/struct, allowing the maps to share their structure in memory. In the example above, both john and meg share the same key structure in memory.

構造体はパターンマッチングでもよく用いられ,構造体の型が同じであることを保証します:

Structs can also be used in pattern matching and they guarantee the structs are of the same type:

iex> %User{name: name} = john
%User{age: 27, name: "john"}
iex> name
"john"
iex> %User{} = %{}
** (MatchError) no match of right hand side value: %{}

マッチングが動作するのは構造体がマップの中に__struct__というフィールドを保存しているからです:

Matching works because structs store a field named __struct__ inside the map:

iex> john.__struct__
User

全体として,構造体は単にデフォルトのフィールドがあるだけの剥き出しのマップです.剥き出しのマップと言ったところに注目してください.マップは構造体のために何のプロトコルも実装していません.例えば構造体を列挙するようなアクセスはできません:

Overall, a struct is just a bare map with default fields. Notice we say it is a bare map because none of the protocols implemented for maps are available for structs. For example, you can't enumerate nor access a struct:

iex> user = %User{}
%User{age: 27, name: "john"}
iex> user[:name]
** (Protocol.UndefinedError) protocol Access not implemented for %User{age: 27, name: "john"}

構造体ディクショナリでもないので,Dictモジュールからも使えません:

A struct also is not a dictionary and therefore can't be used with the Dict module:

iex> Dict.get(%User{}, :name)
** (ArgumentError) unsupported dict: %User{name: "john", age: 27}

Since structs are just maps, they will work with the Map module:

iex> Map.put(%User{}, :name, "kurt")
%User{age: 27, name: "kurt"}
iex> Map.merge(%User{age: 27}, %User{name: "takashi"})
%User{age: 27, name: "takashi"}

次の章でどうやって構造体がプロトコルを使ってやりとりするか見ていきます.

We will cover how structs interacts with protocols in the next chapter.