Elixir defdelegateで委譲


[Elixir]: https://elixir-lang.org/
[Erlang]: https://www.erlang.org/
[Phoenix]: https://www.phoenixframework.org/
[Nerves]: https://hexdocs.pm/nerves
[Livebook]: https://livebook.dev/
[IEx]: https://elixirschool.com/ja/lessons/basics/basics/#%E5%AF%BE%E8%A9%B1%E3%83%A2%E3%83%BC%E3%83%89
[defdelegate/2]: https://hexdocs.pm/elixir/Kernel.html#defdelegate/2
[def/2]: https://hexdocs.pm/elixir/Kernel.html#def/2
[委譲]: https://ja.wikipedia.org/wiki/%E5%A7%94%E8%AD%B2
[IEx.Helpers.recompile/1]: https://hexdocs.pm/iex/IEx.Helpers.html#recompile/1
[関数ドキュメント]: https://elixirschool.com/ja/lessons/basics/documentation/#%E9%96%A2%E6%95%B0%E3%81%AE%E3%83%89%E3%82%AD%E3%83%A5%E3%83%A1%E3%83%B3%E3%83%88%E5%8C%96-3

[Elixir]プログラムの構成を考える時にたまに便利な[defdelegate/2]という関数があります。

> Defines a function that delegates to another module.
>
> Functions defined with defdelegate/2 are public and can be invoked from outside the module they're defined in, as if they were defined using def/2. Therefore, defdelegate/2 is about extending the current module's public API. If what you want is to invoke a function defined in another module without using its full module name, then use alias/2 to shorten the module name or use import/2 to be able to invoke the function without the module name altogether.　

- 他のモジュールに委譲する関数を定義
- [defdelegate/2]で定義された関数はpublicであり、[def/2]で定義された場合と同様に、定義されたモジュールの外から呼び出すことが可能

https://qiita.com/torifukukaiou/items/17d55cf896c24b13350e

論よりRun。

## サンプル[Elixir]プロジェクトを生成

```sh
$ mix new myapp

$ cd myapp

$ ls
README.md lib       mix.exs   test
```

`lib/myapp.ex`の中身はこんなコードになっています。

```elixir
defmodule Myapp do
  @moduledoc """
  Documentation for `Myapp`.
  """

  @doc """
  Hello world.

  ## Examples

      iex> Myapp.hello()
      :world

  """
  def hello do
    :world
  end
end
```

[IEx]内でサンプル[Elixir]プロジェクトを起動し、`Myapp.hello/1`を実行してみます。

```elixir
$ iex -S mix

iex> Myapp.hello()
:world
```

## [defdelegate/2]を用いて委譲

`lib/myapp.ex`の中身を変更します。

- `Myapp.Toukon`モジュールを作り、`hello/0`を定義
- [defdelegate/2]を用いて`Myapp.hello/0`を`Myapp.Toukon.hello/0`に[委譲]

```elixir
defmodule Myapp do
  @moduledoc """
  Documentation for `Myapp`.
  """

  defdelegate hello, to: Myapp.Toukon
end

defmodule Myapp.Toukon do
  @doc """
  闘魂あいさつ
  """
  def hello, do: "元気ですかーーーーッ！"
end
```

[IEx.Helpers.recompile/1]で再コンパイルし、`Myapp.hello/1`をもう一度実行してみます。

```elixir
iex> recompile
Compiling 1 file (.ex)
:ok
iex> Myapp.hello()
"元気ですかーーーーッ！"
```

`Myapp`モジュールの使用者から見ると、`Myapp.hello/0`は普通の関数ですが、実際の処理は`Myapp.Toukon`モジュールへ移譲されています。

## [関数ドキュメント]は委譲されない

あるElixirパッケージの開発をしている最中に気づきました。関数ドキュメントはできればコードの近くに置いておきたいものですが、[defdelegate/2]で別の関数から移譲された場合は関数ドキュメントが`delegate_to: Myapp.Toukon.hello/0`のようになります。自分のコードであればたいして問題にならないのですが、Elixirパッケージの開発ではpublic関数のドキュメントを読みやすくすることが重要です。

![CleanShot 2022-11-22 at 20.04.19@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82804/9c7c2687-7f49-9ca8-ae39-8c5953a3d54f.png)

対策としては、単純に[defdelegate/2]の真上に関数ドキュメントを書くしかなさそうです。

```elixir
@doc """
闘魂あいさつ

## Examples

    iex> Myapp.hello()
    "元気ですかーーーーッ！"

"""
defdelegate hello, to: Myapp.Toukon
```

[Elixir]の実装にも何箇所か出てきます。

https://github.com/elixir-lang/elixir/blob/be8e62d61f90965426e2110c446f986a8665ea99/lib/elixir/lib/process.ex#L83-L103

意外なことに、Webアプリ開発フレームワークの[Phoenix]では1箇所だけしか使用されていないようです。

やっぱり[defdelegate/2]の真上に関数ドキュメントが書かれています。

https://github.com/phoenixframework/phoenix/blob/f095d1f82cdf18ffc69226fd59ce36a7fc2ffb44/lib/phoenix/controller.ex#L1281-L1294

## ご参考までに

https://speakerdeck.com/elijo/elixirkomiyunitei-falsebu-kifang-guo-nei-onrainbian

https://qiita.com/piacerex/items/e0b6e46b1325bb931122

https://qiita.com/torifukukaiou/items/1edb3e961acf002478fd

https://qiita.com/piacerex/items/e5590fa287d3c89eeebf