/var/log/messages

Feb 15, 2019 - 2 minute read - Comments - programming

Metaprogramming Elixir (4)

ゴールは他のモジュールを test macro で拡張すること、らしい。以下な testing framework を、との事です。

defmodule MathTest do
  use Assertion

  test "integers can be added and subtracted" do
    assert 1 + 1 == 2
    assert 2 + 3 == 5
    assert 5 - 5 == 10
  end

  test "integers can be multiplied and divided" do
    assert 5 * 5 == 25
    assert 10 / 2 == 5
  end
end

実行例が以下らしい。

iex> MathTest.run
..
===========================
FAILURE: integers can be added and subtracted
===========================
   Expected:       0
   to be equal to: 10
..:ok

use というソレが初見。caller のコンテキストと一緒に Assertion module なコードを生成しないと、ということなのかどうか。たしかに上のコードの例には run/0 な定義は記述されていません。

モジュール拡張

以下を入力、とあります。

defmodule Assertion do
  defmacro extend(options \\[]) do
    quote do
      import unquote(__MODULE__)
      def run do
        IO.puts "Running the tests ..."
      end
    end
  end
end

defmodule MathTest do
  require Assertion
  Assertion.extend
end

実行してみました。

iex(1)> c "module_extension_custom.exs"
warning: variable "options" is unused
  module_extension_custom.exs:2

[MathTest, Assertion]
iex(2)> MathTest.run
Running the tests ...
:ok

なるほど。Assertion.extend で run が MathTest の手続きになってますね。こうして module を拡張するのか。

use というモジュール拡張のための API

use SomeModuleSomeModule.__using__/1 マクロを invoke なさるとのこと。use を使うために module_extension なナニを書き換えます。

defmodule Assertion do
  defmacro __using__(_options) do
    quote do
      import unquote(__MODULE__)
      def run do
        IO.puts "Running the tests ..."
      end
    end
  end
  defmacro extend(options \\[]) do
    quote do
      import unquote(__MODULE__)
      def run do
        IO.puts "Running the tests ..."
      end
    end
  end
end

defmodule MathTest do
#  require Assertion
  use Assertion
  Assertion.extend
end

動かしてみます。

iex(3)> c "module_extension_use.exs"   
warning: redefining module Assertion (current version defined in memory)
  module_extension_use.exs:1

warning: variable "options" is unused
  module_extension_use.exs:10

warning: redefining module MathTest (current version defined in memory)
  module_extension_use.exs:20

warning: this clause cannot match because a previous clause at line 22 always matches
  module_extension_use.exs:23

[MathTest, Assertion]
iex(4)> MathTest.run
Running the tests ...
:ok

なるほど。これで use を使って機能拡張? ができるようになったのか。Ruby (Rails) で言う include なのかしら。

次は

test マクロな模様。