매크로 훅

매크로 훅 (Macro Hooks)

특정 상황에서 컴파일 타임에 훅(hook)으로 호출되는 특별한 매크로들이 있어요.

  • inherited — 하위 클래스가 정의될 때 호출돼요. @type는 상속하는 타입이에요.
  • included — 모듈이 include될 때 호출돼요. @type는 include하는 타입이에요.
  • extended — 모듈이 extend될 때 호출돼요. @type는 extend하는 타입이에요.
  • method_missing — 메서드를 찾지 못했을 때 호출돼요.
  • method_added — 현재 스코프에 새 메서드가 정의될 때 호출돼요.
  • finished — 파싱이 끝난 뒤 호출돼요. 그래서 모든 타입과 그 메서드를 알 수 있어요.

inherited의 예:

class Parent
  macro inherited
    def lineage
      "{{@type.name.id}} < Parent"
    end
  end
end

class Child < Parent
end

Child.new.lineage # => "Child < Parent"

method_missing의 예:

macro method_missing(call)
  print "Got ", {{call.name.id.stringify}}, " with ", {{call.args.size}}, " arguments", '\n'
end

foo          # Prints: Got foo with 0 arguments
bar 'a', 'b' # Prints: Got bar with 2 arguments

method_added의 예:

macro method_added(method)
  {% puts "Method added:", method.name.stringify %}
end

def generate_random_number
  4
end
# => Method added: generate_random_number

method_missingmethod_added는 모두, 매크로가 정의된 클래스 또는 그 상속 클래스 안의 호출·메서드에만 적용돼요. 클래스 밖에서 정의됐다면 최상위 레벨에만 적용됩니다. 예를 들어:

macro method_missing(call)
  puts "In outer scope, got call: ", {{ call.name.stringify }}
end

class SomeClass
  macro method_missing(call)
    puts "Inside SomeClass, got call: ", {{ call.name.stringify }}
  end
end

class OtherClass
end

# This call is handled by the top-level `method_missing`
foo # => In outer scope, got call: foo

obj = SomeClass.new
# This is handled by the one inside SomeClass
obj.bar # => Inside SomeClass, got call: bar

other = OtherClass.new
# Neither OtherClass or its parents define a `method_missing` macro
other.baz # => Error: Undefined method 'baz' for OtherClass

finished는 타입이 완전히 정의된 뒤에 호출돼요. 이때는 그 클래스에 대한 확장(extension)까지 포함됩니다. 다음 프로그램을 볼게요.

macro print_methods
  {% puts @type.methods.map &.name %}
end

class Foo
  macro finished
    {% puts @type.methods.map &.name %}
  end

  print_methods
end

class Foo
  def bar
    puts "I'm a method!"
  end
end

Foo.new.bar

print_methods 매크로는 만나는 즉시 실행돼요. 그 시점에는 정의된 메서드가 없으므로 빈 리스트를 출력하죠. Foo의 두 번째 선언이 컴파일되면 finished 매크로가 실행돼 [bar]를 출력합니다.

사용하는 매크로 훅에 따라 훅은 쌓이거나(stacked) 덮어써질 수 있어요(overridden).

출처: Crystal 공식 문서 - Hooks

본문

쌓기 (Stacking)

쌓일 때는, 훅이 정의된 코드에서 훅이 정의된 횟수만큼 여러 번 실행돼요. 이런 방식으로 실행되는 훅은 정의 순서대로 실행됩니다. 예를 들어:

# Stack the top-level finished macro
macro finished
  {% puts "I will execute!" %}
end

macro finished
  {% puts "I will also execute!" %}
end

위 예시에서는 finished 매크로가 둘 다 실행돼요. 쌓기는 다음 훅들에서 동작합니다: inherited, included, extended, method_added, finished.

덮어쓰기 (Overriding)

method_missing 매크로 훅의 정의는 같은 코드에서 이 훅의 이전 정의를 덮어써요. 마지막에 정의된 매크로만 실행됩니다. 예를 들어:

macro method_missing(name)
  {% puts "I didnt run! :(" %}
end

class Example
  macro method_missing(name)
    {% puts "I didnt run! :(" %}
  end

  macro method_missing(name)
    {% puts "I am the only one that will run!" %}
  end
end

macro method_missing(name)
  {% puts "I am the only one that will run!" %}
end

Example.new.call_a_missing_method # => I am the only one that will run!

call_a_missing_method # => I am the only one that will run!

더 알아보기 (Learn more)