Elixirで構造体のプロパティを一部共通化する


概要

Elixirで構造体のプロパティを共通化したかったので、メモも兼ねて共有します。

環境

$ elixir -v
Erlang/OTP 22 [erts-10.7.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]

Elixir 1.10.3 (compiled with Erlang/OTP 22)

コード

common_struct.ex

# 共通プロパティを持ち、`use`によって構造体を定義するモジュール
defmodule CommonStruct do
  defmacro __using__(attributes) do
    quote do
      # 共通プロパティ
      common_attributes = [str: "", int: 0]

      # 引数のキーワードリストと一緒に`defstruct`へ渡し、呼び出しモジュールの構造体として定義する。
      common_attributes ++ unquote(attributes) |> defstruct
    end
  end
end
my_struct.ex
defmodule MyStruct do
  # 独自に定義するプロパティとデフォルト値をキーワードリストにして渡す。
  use CommonStruct, bool: false, float: 0.1
end
iex
iex(1)> i %MyStruct{}
Term
  # ちゃんと定義できてます!
  %MyStruct{bool: false, float: 0.1, int: 0, str: ""}
Data type
  MyStruct
Description
  This is a struct. Structs are maps with a __struct__ key.
Reference modules
  MyStruct, Map
Implemented protocols
  IEx.Info, Inspect

# デフォルト値
iex(2)> %MyStruct{}
%MyStruct{bool: false, float: 0.1, int: 0, str: ""}

# 初期値あり
iex(3)> %MyStruct{bool: true, float: 3.14, int: 100, str: "hoge"}
%MyStruct{bool: true, float: 3.14, int: 100, str: "hoge"}

CommonStructには構造体が定義されていないので、上のコードでは%CommonStruct{}を実行するとエラーが発生します。

iex
iex(4)> %CommonStruct{}
** (CompileError) iex:4: CommonStruct.__struct__/1 is undefined, cannot expand struct CommonStruct. Make sure the struct name is correct. If the struct name exists and is correct but it still cannot be found, you likely have cyclic module usage in your code

共通部分も構造体として欲しい場合は、下記のようにすればできます。

common_struct.ex
defmodule CommonStruct do
  defmacro __using__(attributes) do
    quote do
      CommonStruct.common_attributes() ++ unquote(attributes) |> defstruct
    end
  end

  # defstruct, __using__で利用するためにモジュール変数に追い出す
  @common_attributes [str: "", int: 0]
  defstruct @common_attributes
  def common_attributes(), do: @common_attributes
end
iex
iex(1)> i %CommonStruct{}
Term
  %CommonStruct{int: 0, str: ""}
Data type
  CommonStruct
Description
  This is a struct. Structs are maps with a __struct__ key.
Reference modules
  CommonStruct, Map
Implemented protocols
  IEx.Info, Inspect

以上です!

皆さんによきElixirライフを