どうも、靖宗です。
今回はStructsということで構造体?C言語ではないしたぶんもっと違うなにか・・・
- Defining structs(構造体の定義?)
- Accessing and updating structs(構造体の値の取得と更新)
- Structs are bare maps underneath
- Default values and required keys(デフォルト値と必須キー)
まずは、map
のおさらい。
iex> map = %{a: 1, b: 2} %{a: 1, b: 2} iex> map[:a] 1 iex> %{map | a: 3} %{a: 3, b: 2}
map
の作成、参照、更新ですね。
Structsはコンパイル時にチェックとデフォルトの値をつけるmap
の拡張と書かれてますがどういうことなんでしょうか。
とりあえず次へ。
Defining structs(構造体の定義?)
Structの定義はdefstruct
を用いて行うそうです。
iex> defmodule User do ...> defstruct name: "John", age: 27 ...> end
やっぱり構造体って認識で問題無い?
構造体の名前はdefmodule
で宣言し、内部の構造はdefstruct
で作って行くみたいです。
この構造体の定義から構造体を作成するには下記の通りにするようです。
iex> %User{} %User{age: 27, name: "John"} iex> %User{name: "Jane"} %User{age: 27, name: "Jane"}
イメージ的にはクラスからインスタンスを作成するオブジェクト指向的なところを感じますが関数型言語でそのイメージを持つのは危険なのかもしれないです。
一応コンパイル時に内部のフィールドに変数名?キー名?があるかないかはチェックしてくれるようです。
iex> %User{oops: :field} ** (KeyError) key :oops not found in: %User{age: 27, name: "John"}
厳密に型を定義してるわけではないのでおまけみたいに考えておけば良いのかな。
とりあえず次へ。
Accessing and updating structs(構造体の値の取得と更新)
主にmap
と同じ様に扱えるようです。
iex> john = %User{} %User{age: 27, name: "John"} iex> john.name "John" iex> jane = %{john | name: "Jane"} %User{age: 27, name: "Jane"} iex> %{jane | oops: :field} ** (KeyError) key :oops not found in: %User{age: 27, name: "Jane"}
作成時に%
と{}
の間に構造体名があるかないかぐらいの違いでしょうか。
上記の例ではmap
更新の記法|
を使用してますが、構造体の場合は同じキー名で値が同じなら(今回はname
が違うのでコレを除く)、他の構造はメモリ上では共有されるようです。
ここは関数型言語ならではの仕様でしょうか?一般的なオブジェクト指向ならとりあえずインスタンス化してメモリを分けておかないと勝手に更新しちゃいそうですが、基本的に変数の値を変えに行く操作がないので安全なのでしょう。
構造体はパターンマッチも使えるそうです。
iex> %User{name: name} = john %User{age: 27, name: "John"} iex> name "John" iex> %User{} = %{} ** (MatchError) no match of right hand side value: %{}
いろんなところでパターンマッチでてくるなぁ。
高速かつシンプルに書くにはパターンマッチを如何に駆使するかが肝になってきそうです。
Structs are bare maps underneath
構造体は根本的にはフィールドが決まっているmap
であるようです。
iex> is_map(john) true iex> john.__struct__ User
なので、is_map/1
で確認してみてもtrue
が返ってきてます。
また、構造体には__struct__
という特殊なキーが登録されているようです。おそらくElixirはこれで構造体であるか否かを判断しているんじゃないでしょうか。
(ということは後発的な機能?)
また、bare maps
と書かれている由来が、map
のようにアトムでアクセスできないようです。
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> jane = Map.put(%User{}, :name, "Jane") %User{age: 27, name: "Jane"} iex> Map.merge(jane, %User{name: "John"}) %User{age: 27, name: "John"} iex> Map.keys(jane) [:__struct__, :age, :name]
とりあえずMapモジュールでやりとりすることが多いんじゃないでしょうか?
ちょいちょい説明文にprotocols
って出てきてますが、次のチャプターなので気にしないで進みます。
Default values and required keys(デフォルト値と必須キー)
構造体を定義するときにデフォルトの値を入力しないとnil
が代入されるようです。
iex> defmodule Product do ...> defstruct [:name] ...> end iex> %Product{} %Product{name: nil}
一応違う記法でも試す。
iex(10)> defmodule Product do ...(10)> defstruct name: nil ...(10)> end {:module, Product, <<70, 79, 82, 49, 0, 0, 5, 216, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 184, 0, 0, 0, 18, 14, 69, 108, 105, 120, 105, 114, 46, 80, 114, 111, 100, 117, 99, 116, 8, 95, 95, 105, 110, 102, 111, 95, ...>>, %Product{name: nil}} iex(11)> %Product{} %Product{name: nil}
デフォルト値を定義には書かないけど作成時に必須にするには、前回のModule attributesを利用するようです。
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
全体的にちゃんと説明しようとするとこういう構成(Getting-started)になるんでしょうけど、サクッと始めて後から掘り下げる方がいい気がしてきました(今更)。
とはいえあともう少しなのでこのまま進めます。