매크로 훅
매크로 훅 (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_missing과 method_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).
본문
쌓기 (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)
- Crystal 공식 문서 - Hooks
- Macros — 매크로 전체