1. OverView
いやー、タイトルだけ決めて書いたつもりになってますね。
本日は、Elixir Schoolのモジュールの解説、その1となっております。
https://elixirschool.com/ja/lessons/basics/modules
このモジュール、話は簡単なのですが…簡単なのですが、「何に使うか」の部分が理解しにくい。
延々と説明することになりますが、ぶっちゃけ、「後で読む」べきものなのかなと思ってます。
なので、古い言語の、しかも仕事の愚痴を含めた僕のお気持ちポエムみたいになっちゃいますが、
「何に使うのか」を交えて記事にしたいと思います。
…新しい概念を取り落としてたらごめんなさい。(伏線)
2. まずは解説
2.1 モジュール
Elixir Schoolのモジュールの解説から行ってみましょう。
モジュールは関数群を名前空間へと組織する最良の方法です。 関数をまとめることに加えて、関数のレッスンで取り上げた名前付き関数やプライベート関数を定義できます
いきなり飛ばすなぁ。名前空間(namespace)と言う言葉がいきなり出てきますね。このElixir Schoolのスタイル好きよ。
と言うのも、すでに関数の説明の際に、「名前付き関数やプライベート関数」の話をしてますね。
これ以外にも、後で述べる構造体やドキュメントもまとめられるのですが、今はこの二つ、「名前付き関数やプライベート関数」に集中しましょう。
これは、Elixir Schoolのサンプルです。
defmodule Example do
def greeting(name) do
"Hello #{name}."
end
end
iex(2)> Example.greeting "Sean"
"Hello Sean."
Exampleと言うモジュールに、greetingと言う関数が入っています。
greeting、挨拶ですね。まぁ、挨拶するモジュールをたくさん定義することはないでしょう。多分。
では、addだったらどうでしょう?
defmodule Number do
def add(a, b) do
a + b
end
end
defmodule StringOps do
def add(a, b) do
a <> b
end
end
Numberが数字を足す、StringOpsが文字列を結合しております。
…こういう時こそ、パターンマッチでやれって?うん、説明だからおいといてね。
こういう時に、名前空間がないと、nunber_add/2とかstrings_add/2とか名前を作って管理する必要があります。
しかし、NumberもStringOpsも別のモジュール、名前空間にいるので、add/2を意識せずに定義出来ています。
使い方は、
モジュール名.関数名ですね。以下の様に使えます。
iex(4)> Number.add(1,3)
4
iex(5)> StringOps.add("test","test2")
"testtest2"
さらに、モジュールはモジュールを含むことも出来ます。
上の例を別のモジュールに含んでみましょう。
defmodule Mylib do
defmodule Number do
def add(a, b) do
a + b
end
end
defmodule StringOps do
def add(a, b) do
a <> b
end
end
end
使ってみましょう。
iex(4)> Mylib.Number.add(1,3)
4
iex(5)> Mylib.StringOps.add("test","test2")
"testtest2"
名前空間のおかげで、add, Number, StringOpsなどに奇妙な名をつけることなく、関数が呼び出せて
いると思います。まーついでに、「モジュール名.関数名」ですので、「ああ、このモジュールを使ってるんだな」
と明示的にわかるのもよいですねー。
2.2 モジュールの属性(Module Attributes)
さて、モジュールには属性(Attribute, 複数形でAttributes)を定義出来ます。
まずは例を見ましょう。
defmodule Example do
@greeting "Hello"
def greeting(name) do
~s(#{@greeting} #{name}.)
end
end
うん、 ~s(#{@greeting} #{name}.)とかサクッと書かれてますね。これ、シジルと言いまして。。。は
あとやりますので、まずは、@名前で定義されている属性、@greetingがどう評価されるか、確認しましょう。
iex(7)> Example.greeting("mokemoke")
"Hello mokemoke."
見事、@greetingは"Hello"に置き換えられていますね。
この属性には、色々と予約されている属性があるそうです。
フレームワークなどでも定義されておりますが、ちょっと一例を引用しておきます。
Attribute | 説明 |
---|---|
moduledoc | モジュールの説明や使用方法を記載します |
doc | 関数の説明や使用例を記載します。 |
behaviour | モジュールが特定のビヘイビア(振る舞い)を実装していることを示します |
とりあえず、こんなもんで。
これはもっと深堀しないと、チートシートにならないですね。
2.3 構造体(Structs)
…落ち着け、オレ、お前の知ってる構造体とは似て非なるものだ。
さて、ElixirSchoolの説明を見てみましょう。
構造体は定義済みのキーの一群とデフォルト値を持つ特殊なマップです。
モジュール内部で定義されなくてはならず、そのモジュールから名前をとります。
構造体にとっては、モジュール内部で自身しか定義されていないというのもありふれたことです。
サンプルがこちら。defstruct を用い、フィールドとデフォルト値のキーワードリストが添えられております。
defmodule Example.User do
defstruct name: "Sean", roles: []
end
これ、拡張した「型」の様に使えるんですね…古い理解では、OOPのインスタンスを作成しているノリですかね?
こうやって「作成」します。
iex(9)> %Example.User{} # そのまま、default値
%Example.User{name: "Sean", roles: []}
iex(10)> %Example.User{name: "Steve"} # nameにSteveをセット
%Example.User{name: "Steve", roles: []}
iex(11)> %Example.User{name: "Steve", roles: [:manager]} # nameにSteveを, rolesに:managerをセット
%Example.User{name: "Steve", roles: [:manager]}
ちょっとわかりづらいのですが、次の例を見てみましょう。
「構造体はあたかもマップのように更新することができます」
iex(12)> steve = %Example.User{name: "Steve"} # name:を"Steve"にして、steveと言う変数にバインド
%Example.User{name: "Steve", roles: []}
iex(13)> steve # steveの中身を確認しております。
%Example.User{name: "Steve", roles: []}
iex(14)> sean = %{steve | name: "Sean"} # steveの名前だけSeanに変更して、seanと言う変数にバインド
%Example.User{name: "Sean", roles: []}
iex(15)> sean
%Example.User{name: "Sean", roles: []}
iex(16
構造体のフィールドに、以下の様にアクセス出来ます。
まー、マップなんだから当然な気もしますが。
iex(18)> sean.name
"Sean"
さて、ここまで忘れないうちに書いておきましょう。
最も重要なことですが、構造体はマップに対してマッチすることができます
iex(16)> %{name: "Sean"} = sean
%Example.User{name: "Sean", roles: []}
2.4 構造体(Structs)のinspect・カスタムイントロスペクション
モジュールの属性の使用例でもあるので、章を分けました。
前の章で作った構造体、seanをinspectして見てみましょう。
iex(20)> inspect(sean)
"%Example.User{name: \"Sean\", roles: []}"
ごっそり中が出てますね。
では、見せたくないものを隠すのに、@deriveを使用します。
defmodule Example.User do
@derive {Inspect, only: [:name]}
defstruct name: nil, roles: []
end
iex(24)> sean = %Example.User{name: "Sean"}
#Example.User<name: "Sean", ...>
iex(25)> inspect(sean)
"#Example.User<name: \"Sean\", ...>"
と、アトリビュート@deriveに定義したとおり、:nameのみが表示されております。
さて、本日はこれでおしまい。
3. 本日のチートシート
項目 | 説明 | 例 |
---|---|---|
モジュールとは? | モジュールは、関数やマクロ、構造体などをまとめて管理するための単位です。defmoduleで定義します | defmodule Example do def greeting(name) do "Hello #{name}." end end |
モジュールはモジュールを含むことも出来ます | defmodule Mylib do defmodule Number do def add(a, b) do a + b end end defmodule StringOps do def add(a, b) do a <> b end end end |
|
モジュールの属性 | モジュールには属性(Attribute, 複数形でAttributes)を定義出来ます。@Attribute名で指定します | defmodule Example do @greeting "Hello" def greeting(name) do ~s(#{@greeting} #{name}.) end end |
構造体(Structs)の定義 | 定義済みのキーの一群とデフォルト値を持つ特殊なマップです。defstruct を用い、フィールドとデフォルト値のキーワードリストが添えます | defmodule Example.User do defstruct name: "Sean", roles: [] end |
構造体の作成方法 | %Example.User{name: "Steve"} | |
構造体はマップに対してマッチすることができます | iex(16)> %{name: "Sean"} = sean %Example.User{name: "Sean", roles: []} |
|
構造体(Structs)のinspect・カスタムイントロスペクション | defmodule Example.User do @derive {Inspect, only: [:name]} defstruct name: nil, roles: [] end |