技術メモ

プログラミングとか電子工作とか

Elixir入門(第十四章 Module attributes)

f:id:ysmn_deus:20190122112104p:plain

どうも、靖宗です。
今回はモジュールの属性?モジュールを組み立てていくときに必要な情報っぽいです。

Module attributesというのはElixirで

  1. モジュールにユーザーかVMの為の注釈を付ける
  2. 定数として機能する
  3. コンパイル時にモジュールの一時的なストレージ(temporary module storage)として利用される

意味不明な単語が最後に出てきましたが、順に見ていきましょう。

注釈として

Erlang由来の機能?モジュールにバージョンとかを記載できる。

defmodule MyServer do
  @vsn 2
end

@vsnというのがバージョンを示す属性なんだそうで。VMでアップデートが必要かどうかの判断に使われるとのこと。
これも実際使われてるコードを見ないことにはピンときませんね。

よく使う属性を書いてくれてます。

  • @moduledoc モジュールのドキュメント
  • @doc 関数やマクロのドキュメント
  • @behaviour OTPの詳細?OTPがなんなのかわかんないんでよくわからん。たぶん振る舞い(適当)
  • @before_compile コンパイルされる前に発動するhook?('ω')。o(hook????????????)

@moduledoc@docはしょっちゅう使うみたい。というか使えと。
可読性が下がらない程度にはドキュメントは書いた方が良さげ。

以前書いたmath.exを改良する。

defmodule Math do
  @moduledoc """
  Provides math-related functions.

  ## Examples

      iex> Math.sum(1, 2)
      3

  """

  @doc """
  Calculates the sum of two numbers.
  """
  def sum(a, b), do: a + b
end

Markdownが使えるあたり、ドキュメントを生成するなにがしかがありそう。
一応、上の奴をコンパイルしてiex上で見てみる。h モジュール(関数)で見れる。

iex(1)> h Math
* Math

Provides math-related functions.

## Examples

    iex> Math.sum(1, 2)
    3


iex(2)> h Math.sum
* def sum(a, b)

Calculates the sum of two numbers.

ExDocって奴を使うとHTMLでドキュメントが出力できる。
Markdownで書かれている旨みがこういう所ででてくるんですね。

"constants"として

Elixirの開発者はよくモジュールの属性を定数として使うそうです。

defmodule MyServer do
  @initial_state %{host: "127.0.0.1", port: 3456}
  IO.inspect @initial_state
end

なるほど。
値を付けてない定数も書けるには書けるが警告が出る模様。

defmodule MyServer do
  @unknown
end
warning: undefined module attribute @unknown, please remove access to @unknown or explicitly set it before access

使う事は無さそう?
属性は内部の関数でも読むことができる。

defmodule MyServer do
  @my_data 14
  def first_data, do: @my_data
  @my_data 13
  def second_data, do: @my_data
end

MyServer.first_data #=> 14
MyServer.second_data #=> 13

モジュール内で何回も定義するのはなんかおかしい気もしますが、上記のコードはコメントの通りになるそうです。
ただ、この定数はコンパイル時に保持しているだけで実行の際に参照されている訳では無さそう。つまり、上記のコードはコンパイルされれば

defmodule MyServer do
  def first_data, do: 14
  def second_data, do: 13
end

となっている筈。

あと、属性を定義するときは属性名とその値の間に改行はいれたらダメだそうです。

一時的なストレージとして

ElixirのWebフレームワーク?のPlugが例に出ています。

defmodule MyPlug do
  use Plug.Builder

  plug :set_header
  plug :send_ok

  def set_header(conn, _opts) do
    put_resp_header(conn, "x-header", "set")
  end

  def send_ok(conn, _opts) do
    send(conn, 200, "ok")
  end
end

IO.puts "Running MyPlug with Cowboy on http://localhost:4000"
Plug.Adapters.Cowboy.http MyPlug, []

ここでplug/1というマクロはウェブリクエストがあったときに関数とつなげるマクロだそうです。
('ω')。o(????????????)
全然意味が分からない・・・
コンパイル直前にPlugの内部にあるcall/2という関数が実行される?
メタプログラミング的な処理なんでしょうか、イマイチよくわかりません。
もうここではおまじない程度に考えておいた方が良さそうです。戦略的後回し!

以前に出てきたExUnitの例も出てきてたので一応記載。

defmodule MyTest do
  use ExUnit.Case

  @tag :external
  test "contacts external service" do
    # ...
  end
end

これを見るとやはり@tagというのがコンパイル時になにがしかの関数に変換され動作する気がするんですよね。
なにやら「メタプログラミングの項目見てくれ」的な事も書いてますし、ここでは深追いしない方が良さげです。