Elixir勉強日記6 〜モジュール編〜
Elixir勉強日記6回目です
前回で関数まわりに触って若干お腹いっぱいになってサボり過ぎました
続きを進めます
モジュール... defmodule
的な?
モジュール
私たちは経験的に、全ての関数を1つの同じファイルとスコープに持つと手に負えないことを知っています。 このレッスンでは関数をまとめ、構造体として知られる特別なマップを定義することで、コードをより効率のよい形に組織化する方法を取り上げます。
スコープを切り分けるためにあるんですね(情弱)
モジュール
モジュールは関数群を名前空間へと組織する最良の方法です。 関数をまとめることに加えて、関数のレッスンで取り上げた名前付き関数やプライベート関数を定義できます。
落ち着いて考えたらクラスないしないと困りますね
基本的な例を見てみましょう:
defmodule Example do def greeting(name) do "Hello #{name}." end end iex> Example.greeting "Sean" "Hello Sean."
Elixirではモジュールをネストすることが可能で、機能ごとにさらなる名前空間をつけることができます:
defmodule Example.Greetings do def morning(name) do "Good morning #{name}." end def evening(name) do "Good night #{name}." end end iex> Example.Greetings.morning "Sean" "Good morning Sean."
.
でネストの表現するんですね〜(Ruby
だと:
)
モジュールの属性(attribute)
モジュール属性はElixirでは一般に定数として用いられることがほとんどです。 単純な例を見てみましょう:
defmodule Example do @greeting "Hello" def greeting(name) do ~s(#{@greeting} #{name}.) end end
どうゆうことだってばよ...
重要なので言及しておきますと、 Elixir には予約されている属性があります。 もっとも一般的なのは以下の3つです:
moduledoc
— 現在のモジュールにドキュメントを付けます。doc
— 関数やマクロについてのドキュメント管理。behaviour
— OTP またはユーザが定義した振る舞い(ビヘイビア)に用います。
あーー属性ってのはモジュール内で共通して扱う値のことなんですね
日本語にするとなんか微妙にぱっとしないですね
調べたら属性 => attributes
でした
日本語分かりくすぎる
構造体(Structs)
構造体は定義済みのキーの一群とデフォルト値を持つ特殊なマップです。 モジュール内部で定義されなくてはならず、そのモジュールから名前をとります。 構造体にとっては、モジュール内部で自身しか定義されていないというのもありふれたことです。
構造体ってきくとCを思い出しますが...
モジュール内で定義されるマップの属性みたいな感じなんすね
構造体を定義するには defstruct を用い、フィールドとデフォルト値のキーワードリストを添えます:
defmodule Example.User do defstruct name: "Sean", roles: [] end
いくつか構造体を作ってみましょう:
iex> %Example.User{} #Example.User<name: "Sean", roles: [], ...> iex> %Example.User{name: "Steve"} #Example.User<name: "Steve", roles: [], ...> iex> %Example.User{name: "Steve", roles: [:manager]} #Example.User<name: "Steve", roles: [:manager]> # 構造体はあたかもマップのように更新することができます: iex> steve = %Example.User{name: "Steve"} #Example.User<name: "Steve", roles: [...], ...> iex> sean = %{steve | name: "Sean"} #Example.User<name: "Sean", roles: [...], ...>
構造体はマップのように更新できるのね...
パターンマッチングが未だに慣れない...
最も重要なことですが、構造体はマップに対してマッチすることができます:
iex> %{name: "Sean"} = sean #Example.User<name: "Sean", roles: [...], ...>
Elixir 1.8以降、構造体にカスタムイントロスペクション機能が追加されました。
かすたむいんとろすぺくしょん...
カスタムイントロスペクションがどのように使われるのかを理解するため、 sean の中身を見てみましょう。
iex> inspect(sean) "#Example.User<name: \"Sean\", roles: [...], ...>"
この例では全てのフィールドが出力対象になっていますが、出力したくない項目がある場合、どのようにしたら良いでしょうか? この場合、 @derive を利用することで実現することができます! roles を出力から除外したい場合、以下のように記述します。
defmodule Example.User do @derive {Inspect, only: [:name]} defstruct name: nil, roles: [] end # 注記: @derive {Inspect, except: [:roles]} でも実現することができます。
モジュールを更新したら、 iex で確認してみましょう。
iex> sean = %Example.User{name: "Sean"} #Example.User<name: "Sean", ...> iex> inspect(sean) "#Example.User<name: \"Sean\", ...>" # roles が出力から除外されました!
モジュールの構造体から値を絞って出力するってことなんすかね
クラスのインスタンス変数のアクセス権的な扱いみたいになってるのでしょうか...?
いやモジュールを直で叩いたら構造体の全部の値出てくるから違うか(普段からinspect
でモジュールを呼べってことなのか)
コンポジション(Composition)
さて、モジュールと構造体の作り方がわかったので、コンポジションを用いてモジュールや構造体に既存の機能を追加する方法を学びましょう。 Elixir は他のモジュールと連携する様々な方法を用意しています。
他のモジュールと連携
alias
モジュール名をエイリアスすることができます。 Elixir のコードでは頻繁に使われます:
defmodule Sayings.Greetings do def basic(name), do: "Hi, #{name}" end defmodule Example do alias Sayings.Greetings def greeting(name), do: Greetings.basic(name) end
これを使うことでモジュール間でメソッドのやり取り(継承に近い?)感じなんですね
alias を使わない場合
defmodule Example do def greeting(name), do: Sayings.Greetings.basic(name) end
defmodule Example do alias Sayings.Greetings, as: Hi def print_message(name), do: Hi.basic(name) end
扱いやすい名前で扱えるのはありがたいですね
複数のモジュールを一度にエイリアスすることも可能です:
defmodule Example do alias Sayings.{Greetings, Farewells} end
便利!
import
モジュールをエイリアスするよりも、関数を取り込みたいという場合には、 import を使います:
iex> last([1, 2, 3]) ** (CompileError) iex:9: undefined function last/1 iex> import List nil iex> last([1, 2, 3]) 3
あっ普通はimport
つかうんだ..
フィルタリング
デフォルトでは全ての関数とマクロが取り込まれますが、 :only や :except オプションを使うことでフィルタすることができます。
まあ全部使うわけじゃないですしね
python
とかでよく見かける気がします
特定の関数やマクロを取り込むには、名前/アリティのペアを :only や :except に渡す必要があります。 last/1 で最後の関数のみを取り込んでみましょう:
iex> import List, only: [last: 1] iex> first([1, 2, 3]) ** (CompileError) iex:13: undefined function first/1 iex> last([1, 2, 3]) 3
last/1 で指定された関数以外を全て取り込むには、同じ関数で試してみましょう:
iex> import List, except: [last: 1] nil iex> first([1, 2, 3]) 1 iex> last([1, 2, 3]) ** (CompileError) iex:3: undefined function last/1
名前とアリティのペアを渡すのってアリティ指定するのちょっとだるいですね
名前/アリティのペアに加えて、 :functions と :macros という2つの特別なアトムもあります。これらはそれぞれ関数とマクロのみを取り込みます:
import List, only: :functions import List, only: :macros
どうでもいいけど:macros
ってマクロスっぽい
require
他のモジュールのマクロを使用することをElixirに伝えるために require を使うことができます。 import とのわずかな違いは、関数ではなくマクロを使用可能とすることです。
defmodule Example do require SuperMacros SuperMacros.do_stuff end
マクロを呼び出すのが require
ね
まだロードされていないマクロを呼びだそうとすると、Elixir はエラーを発生させます。
それはそう
use
use マクロを用いることで他のモジュールを利用して現在のモジュールの定義を変更することができます。 コード上で use を呼び出すと、実際には提供されたモジュールに定義されている using/1 コールバックを呼び出します。 using/1 マクロの結果はモジュールの定義の一部になります。 この動作に対する理解を深めるために簡単な例を見ましょう:
ほかのモジュールを利用して現在のモジュールの定義を変更する...?
defmodule Hello do defmacro __using__(_opts) do quote do def hello(name), do: "Hi, #{name}" end end end
マクロについても多少調べたほうがええんかな
とりあえずuse
で呼び出すと__using__
が呼び出されるって感じなんですね
__using__
って名前Python
に似てる気がしますね
ここでは hello/1 関数を定義する using/1 コールバックを定義した Hello モジュールを作りました。 この新しいコードを試すために新しいモジュールを作ります:
defmodule Example do use Hello end
IExでこのコードを試して見ると Example モジュールで hello/1 を使えるのがわかります。
iex> Example.hello("Sean") "Hi, Sean"
使えるようになる...???
defmacro __using__
が走ってhello
メソッドが生えてるってことかな
ここで use が Hello の using/1 コールバックを呼び出して、結果のコードをモジュールに追加します。 基本的な例を見せたので、ここからはこのコードを変更して using/1 にオプションをサポートする方法を見てみましょう。 greeting オプションを追加します:
defmodule Hello do defmacro __using__(opts) do greeting = Keyword.get(opts, :greeting, "Hi") quote do def hello(name), do: unquote(greeting) <> ", " <> name end end end
新しく作った greeting オプションを含むために Example モジュールを更新します:
defmodule Example do use Hello, greeting: "Hola" end
IExで試して見ると挨拶が変わるのを確認できます。
iex> Example.hello("Sean") "Hola, Sean"
これらは use がどうやって動作するのかを説明する簡単な例でしたが、これは Elixir のツールボックスで信じられないほどに強力なツールです。 Elixir を学び続けたら use をあっちこっちで見ることになるでしょう。かならず見ることになりそうな例をひとつあげれば、 use ExUnit.Case, async: true です。
うーん実際にどう使うと便利なのかイメージがなかなかつかないので、使われているところを見たら考えよう(思考停止)
注意: quote 、 alias 、 use 、 require はメタプログラミングで使用してたマクロです。
まとめ
defmodule
でモジュール化できる- モジュールで扱う
attributes
をもつことができる defstruct
を使うことでモジュールの値を構造体(Structs)としてマップのように扱うことができる- モジュール間を色々つなぎこむことができる
alias
で別のモジュールのメソッドをコピー?というか扱えるようになる- 普通は
alias
ではなくimport
でメソッドとマクロをごっそり取り込む require
でモジュールのマクロを使用可能にするuse
でモジュールでdefmacro __using__
で定義されたマクロが走る