Macro 모듈

Macro 모듈

Macro 모듈은 AST를 다루고 매크로를 구현하는 함수들을 제공해요. 매크로는 컴파일 타임 구성요소로, Elixir의 AST를 입력으로 받아 Elixir의 AST를 출력으로 돌려줍니다.

이 모듈의 많은 함수는 정확히 Elixir AST를 순회·조회·변환하기 위해 존재해요.

함수와 매크로의 차이를 보여주는 간단한 예를 볼게요.

defmodule Example do
  defmacro macro_inspect(value) do
    IO.inspect(value)
    value
  end

  def fun_inspect(value) do
    IO.inspect(value)
    value
  end
end

정수를 인자로 넘기면 둘은 같게 동작해요. 하지만 표현식을 넘기면 달라져요.

macro_inspect(1 + 2)
#=> {:+, [line: 3], [1, 2]}
#=> 3

fun_inspect(1 + 2)
#=> 3
#=> 3

매크로는 인자로 주어진 **코드의 표현(AST)**을 받고, 함수는 코드의 실행 결과를 받아요. 매크로는 코드 표현의 상위집합을 돌려줘야 해요. 자세한 내용은 t:input/0t:output/0을 참고하세요.

Elixir AST와 그 구성에 대해 더 배우려면 quote/2를 보세요.

코드 평가 {: .tip}

이 모듈의 함수는 코드를 평가하지 않아요. 사실 매크로에서 코드를 평가하는 것은 종종 안티 패턴이에요. 코드 평가는 Code 모듈을 참고하세요.

출처: Macro

본문

주요 타입

@type input() ::
  input_expr() | {input(), input()} | [input()] | atom() | number() | binary()

매크로의 입력이에요.

@type metadata() :: keyword()

AST 메타데이터의 키워드 리스트예요. Elixir AST의 메타데이터는 값들의 키워드 리스트예요. 어떤 키든 쓸 수 있고 컴파일러의 다른 부분이 다른 키를 쓸 수 있어요. 예를 들어 매크로가 받는 AST는 항상 :line 어노테이션을 포함하지만, quote/2가 만들어낸 AST는 특정 조건에서만 :line을 가져요.

@type escape_opts() :: [
  unquote: boolean(),
  prune_metadata: boolean(),
  generated: boolean()
]

주요 함수

expand/2, expand_once/2, expand_literals/2

expand(ast, env)
expand_once(ast, env)
expand_literals(ast, env)

expand/2는 매크로를 재귀적으로 모두 펼치는 반면, expand_once/2는 가장 바깥쪽 한 번만 펼쳐요. expand_literals/2는 리터럴을 해당 문맥에서 펼쳐요(예: 매크로가 함수 안에서 쓰일 모듈 참조를 확장할 때 %{__CALLER__ | function: {:call, 2}}같은 환경을 넘김).

escape/2

escape(term, opts \ [])

term을 AST로 변환해요. 옵션으로 :unquote, :prune_metadata, :generated를 넘길 수 있어요.

decompose_call/1

decompose_call(ast)
decompose_call(ast, arity)

호출을 {function, arguments} 또는 {module, function, arguments}로 분해해요. 분해할 수 없으면 :error를 돌려줍니다.

pipe/3

pipe(expr, call, position \ :left)

expr |> call 형태의 파이프라인 AST를 만들어요. position은 파이프된 인자가 왼쪽(:left)인지 오른쪽(:right)인지 정해요.

postwalk/2, prewalk/2, postwalk/3, prewalk/3

prewalk(ast, fun)
postwalk(ast, fun)

AST를 전위(prewalk) 또는 후위(postwalk) 순회하며 주어진 함수를 적용해 변환해요. 접기(fold)를 같이 적용하는 /3 변형도 있어요.

generate_arguments/2, var/2, uniq/1, struct!/2, to_string/2, inspect_atom/2

  • generate_arguments(amount, context) — 주어진 컨텍스트의 고유한 변수 인자 리스트를 만들어요.
  • var(atom, context) — 변수 노드를 만들어요.
  • uniq(ast) — AST에서 중복된 리터럴을 제거해요.
  • struct!(module, env) — 주어진 모듈의 구조체를 컴파일 타임에 만들어요(환경에서 구조체 필드를 검사).
  • to_string(ast, fun) — AST를 문자열로 변환해요.
  • inspect_atom(atom, opts) — inspect 옵션에 따라 아톰 문자열을 만드는 데 쓰여요.

has_sequence?/1, update_meta/2, traverse/4, validate/2

has_sequence?/1은 AST에 시퀀스(리스트)가 있는지 검사하고, update_meta/2는 AST 노드의 메타데이터를 갱신해요. traverse/4는 AST를 순회하며 주어진 함수로 변환하고, validate/2는 주어진 AST가 합법적인지 검증해요.

더 알아보기

  • Kernel.SpecialForms.quote/2: 매크로에서 코드를 만드는 특수 형식
  • Kernel.SpecialForms.unquote/1: quote 안에서 표현식을 다시 평가
  • Code 모듈: 코드 평가와 컴파일
  • Meta-programming anti-patterns: 매크로 사용 시 피해야 할 함정