Elixir Macro.Env構造体


[Elixir]: https://elixir-lang.org/
[Erlang]: https://www.erlang.org/
[Macro.Env]: https://hexdocs.pm/elixir/Macro.Env.html
[マクロ]: https://elixirschool.com/ja/lessons/advanced/metaprogramming/#%E3%83%9E%E3%82%AF%E3%83%AD-2
[コンパイル]: https://elixirschool.com/ja/lessons/basics/mix/#%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%AB-2
[構造体]: https://elixirschool.com/ja/lessons/basics/modules#%E6%A7%8B%E9%80%A0%E4%BD%93-2
[Phoenix Framework]: https://www.phoenixframework.org/

## [Macro.Env]

- [コンパイル]時の環境情報を保持する[構造体]。
- 手動で変更してはいけない。

## `__ENV__/0`

現在の環境は、いつでも`__ENV__/0`で取得できます。

```elixir
iex> __ENV__
#Macro.Env<
  aliases: [],
  context: nil,
  ...
>

iex> __ENV__ |> Map.keys()
[:__struct__, :aliases, :context, :context_modules, :file, :function,
 :functions, :lexical_tracker, :line, :macro_aliases, :macros, :module,
 :requires, :tracers, :versioned_vars]
```

直接アクセスしてはいけないフィールドがいくつかあります。[Elixir]内部のマクロ展開機構専用とのことです。

- `:aliases`
- `:functions`
- `:macro_aliases`
- `:macros`
- `:lexical_tracker`
- `:requires`
- `:tracers`
- `:versioned_vars`

とはいうものの現在の環境でどの関数が使えるのかが確認できると便利な場合がある気がします。
こっそり中身を見てみます。

```elixir
iex> __ENV__.functions
[
  {IEx.Helpers,
   [
     break!: 3,
     break!: 4,
     breaks: 0,
     c: 1,
     c: 2,
     cd: 1,
     clear: 0,
     continue: 0,
     exports: 0,
     exports: 1,
     flush: 0,
     h: 0,
     i: 0,
     i: 1,
     l: 1,
     ls: 0,
     ls: 1,
     n: 0,
     next: 0,
     nl: 1,
     nl: 2,
     open: 0,
     pid: 1,
     pid: 3,
     port: 1,
     port: 2,
     pwd: 0,
     r: 1,
     recompile: 0,
     recompile: 1,
     ref: 1,
     ref: 4,
     remove_breaks: 0,
     remove_breaks: 1,
     reset_break: 1,
     reset_break: 3,
     respawn: 0,
     runtime_info: 0,
     runtime_info: 1,
     v: 0,
     v: 1,
     whereami: 0,
     whereami: 1
   ]},
  {Kernel,
   [
     !=: 2,
     !==: 2,
     *: 2,
     **: 2,
     +: 1,
     +: 2,
     ++: 2,
     -: 1,
     -: 2,
     --: 2,
     /: 2,
     <: 2,
     <=: 2,
     ==: 2,
     ===: 2,
     =~: 2,
     >: 2,
     >=: 2,
     abs: 1,
     apply: 2,
     apply: 3,
     binary_part: 3,
     binary_slice: 2,
     binary_slice: 3,
     bit_size: 1,
     byte_size: 1,
     ceil: 1,
     div: 2,
     elem: 2,
     exit: 1,
     floor: 1,
     function_exported?: 3,
     get_and_update_in: 3,
     get_in: 2,
     hd: 1,
     inspect: 1,
     inspect: 2,
     is_atom: 1,
     is_binary: 1,
     is_bitstring: 1,
     is_boolean: 1,
     is_float: 1,
     is_function: 1,
     is_function: 2,
     is_integer: 1,
     is_list: 1,
     is_map: 1,
     is_map_key: 2,
     is_number: 1,
     is_pid: 1,
     is_port: 1,
     is_reference: 1,
     is_tuple: 1,
     length: 1,
     macro_exported?: 3,
     make_ref: 0,
     map_size: 1,
     max: 2,
     min: 2,
     node: 0,
     node: 1,
     not: 1,
     pop_in: 2,
     put_elem: 3,
     put_in: 3,
     rem: 2,
     round: 1,
     self: 0,
     send: 2,
     spawn: 1,
     spawn: 3,
     spawn_link: 1,
     spawn_link: 3,
     spawn_monitor: 1,
     spawn_monitor: 3,
     struct: 1,
     struct: 2,
     struct!: 1,
     struct!: 2,
     throw: 1,
     tl: 1,
     trunc: 1,
     tuple_size: 1,
     update_in: 3
   ]}
]
iex>
```

## `__CALLER__/0`

[マクロ]の内部では、`__CALLER__/0 `により呼び出し元の環境が取得できます。`__ENV__/0`とは異なり、[マクロ]の内部でしか使用できません。

```elixir
iex> __CALLER__
** (CompileError) iex:3: __CALLER__ is available only inside defmacro and defmacrop
    (elixir 1.14.2) src/elixir.erl:376: :elixir.quoted_to_erl/4
    (elixir 1.14.2) src/elixir.erl:277: :elixir.eval_forms/4
    (elixir 1.14.2) lib/module/parallel_checker.ex:107: Module.ParallelChecker.verify/1
    (iex 1.14.2) lib/iex/evaluator.ex:329: IEx.Evaluator.eval_and_inspect/3
    (iex 1.14.2) lib/iex/evaluator.ex:303: IEx.Evaluator.eval_and_inspect_parsed/3
    (iex 1.14.2) lib/iex/evaluator.ex:292: IEx.Evaluator.parse_eval_inspect/3
    (iex 1.14.2) lib/iex/evaluator.ex:187: IEx.Evaluator.loop/1
```

試しに検証用マクロを書きます。

```elixir
defmodule MyMacro do
  defmacro inspect_caller() do
    # 呼び出し元の環境
    IO.inspect(__CALLER__)

    # Elixir's AST (Abstract Syntax Tree)
    quote do
      IO.puts("hello")
    end
  end
end

require MyMacro

MyMacro.inspect_caller()
```

:tada:


## [Phoenix Framework]

[Phoenix Framework]で`__ENV__/0`と`__CALLER__/0`が何箇所かで登場します。参考になるかもしれません。知らんけど。

https://github.com/phoenixframework/phoenix/search?q=%5F%5FENV%5F%5F

https://github.com/phoenixframework/phoenix/search?q=%5F%5FCALLER%5F%5F

## ご参考までに

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

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

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