最近、Erlangのある書籍を読み始めました。そこでは始めの方にErlangの基本的な事柄が書かれていたので、そこを軽く追いながらErlangを軽く学んでいます。(と言っても、Elixirやってきたので文法以上は特に目新しいものはないのですが)
そこで、ElixirのStructとElixir/ErlangのRecordの違いが気になったので、まとめておきます。
結局は、RecordとMapの違いになってきて、Erlangコミュニティで話されるその違いと同じような感じになっていました。(すごいE本のP. 567とか。やっぱりこの本、すごい。)ただ、ElixirのStruct自体はElixir独自のものなので、ちゃんと残しておきます。
Erlang x Record
Erlangでは、Recordはtupleの糖衣構文。例えば、以下の通り
-module (record).
-compile (export_all).
-record(robot, {name,
type=industrial,
hobbies,
details=[]}).
first_robot() ->
#robot{name="Machatron",
type=handmade,
details=["Moved by a small man insides"]}.
を実行すると以下の結果になります。
1> c(record).
{ok,record}
2> record:first_robot().
{robot,"Machatron",handmade,undefined,
["Moved by a small man insides"]}
3> rr(record).
[robot]
4> record:first_robot().
#robot{name = "Machatron",type = handmade,
hobbies = undefined,
details = ["Moved by a small man insides"]}
なるほど。
Elixir x Record
iex(4)> defmodule MR do
...(4)> require Record
...(4)> Record.defrecord :user, name: "Machatron", age: 29
...(4)> end
{:module, MR,
<<70, 79, 82, 49, 0, 0, 6, 184, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 240, 131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115, 95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>,
{:user, 2}}
iex(5)> MR.user
user/0 user/1 user/2
iex>(5) MR.user
{:user, "kazu", 29}
なるほど。でも、こう見るとこれはstructに似ている。と思いますよね。普通にElixirのget startを学んだだけだとRecordがでてこないので、考える順としてはこんな感じだと思われます。
iex(19)> defmodule MS do
...(19)> defstruct [name: "kazu", age: 29]
...(19)> end
{:module, MS,
<<70, 79, 82, 49, 0, 0, 5, 16, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 133, 131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115, 95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>,
%MS{age: 29, name: "kazu"}}
iex(20)> MS.__struct__
%MS{age: 29, name: "kazu"}
ElixirのRecord
ElixirのRecordのドキュメントを見ると
- to work with short, internal data
- to interface with Erlang records
と、Recordの使うポイントが書かれています。ふと、ここでよく見るとstructとの使いわけが気になります。
まぁ、そう思うよね、と思ったらGoogle Groupsにやっぱり。
- https://groups.google.com/forum/#!topic/elixir-lang-talk/6kn7J2XnFg8
- https://gist.github.com/josevalim/b30c881df36801611d13
結論だけここに残しておくと、たいていの場合はStructを使って、限られたときだけRecordを使いましょう、というもの。StructはMapで __struct__ 定義されます。
To clarify: when we said that Records in Elixir were deprecated, it was Elixir implementation of records which is long gone by now.
Recordはcompile time、MapはRuntimeなので、errorを出すタイミングが異なる、など理由をいくつか挙げてたりします。Recordを使う時は、loop内であったりエラーを気にしなくて良い時とか、そういう時に高速に使いたい場合、といった限られていると記載しています。
ちなみに、
Structs offer a mixture between records and maps. Records are compile-time based, maps are runtime based. Structs aim to add record-like compile time checks on top of maps. This is actually faster and conceptually simpler than trying to add runtime features to Records.
と書いているように、ElixirのStructはコンパイルタイムのチェックもされます。実際に使ってみると、keyに対して代入される初期値はcompile-timeの物です。
Recordが使われているところ
Recordが実際に使われているライブラリあるのかな、と思ってPlugを参考にしてみました。すると、Recordは1か所だけで使われていた。それはErlangのrecordから値を引っ張ってくるところ。
ここを実行してみると、確かにerlangの物を持ってきています。
iex> Record.extract(:file_info, from_lib: "kernel/include/file.hrl") [size: :undefined, type: :undefined, access: :undefined, atime: :undefined, mtime: :undefined, ctime: :undefined, mode: :undefined, links: :undefined, major_device: :undefined, minor_device: :undefined, inode: :undefined, uid: :undefined, gid: :undefined]
これで、↑の値が Plug.Static.file_info で取得できるわけですね。
なるほどー。
締め
Erlang書いていると、文法の並び以外は思いの外Elixirだった。(まぁ、ElixirがErlang母体なのでそうなのですが)
以前いただいたメンションであった以下の正しさがわかってしまった。
実際、Erlangの “;” や “.” 、大文字開始の変数とかの使い方付近に不慣れなところが多いのですが、それを除くとElixir書いている時と同じ脳みその使い方している感じです。
ただ、個人的にはpipelineで処理をつなげるような記述の方が考えやすかったので、Elixirの方が文法は好みな感じですね。
あの本はちゃんと読んでいこう。