티스토리 뷰
오늘의 포스팅 내용은 Ruby Metaprogramming 이다.
포스팅의 내용은 사내에서 Ruby Metaprogramming 에 대한 발표를 위해서 아래의 책을 읽고 내용들을 정리한것이다.
내용이 많기때문에 시리즈로 소개할 예정이다.
저번 포스팅(Object Model - http://idea-sketch.tistory.com/36 )에 이어서 다음편이다.
함수를 사용하면 무슨일이 생길까?
Ruby에서는 함수를 사용할때 아래의 2가지 작업을 한다.
- 해당 함수를 찾는다. 이 과정을 Method Lookup 이라고 한다.
- 해당 함수를 실행한다. 함수를 실행하기 위해서 Ruby는 self 를 필요로 한다.
Method Lookup
가장 간단한 상황을 예로들면 Ruby는 함수를 사용할때 해당 객체의 클래스를 확인하고 그곳에서 함수를 찾는다.
조금더 복잡한 상황을 설명하려면 우선 receiver 와 ancestors chain 에 대해서 이해해야 한다.
receiver는 함수를 호출한 Object 이다. 예를들어 my_class.hello( ) 를 호출했을때 my_class는 receiver 이다.
ancestors chain 은 함수를 호출한 my_class 에서 해당 함수가 없을경우 my_class 의 superclass 에서 다시 함수를 찾고
그곳에도 없을경우 superclass의 superclass에서 해당 함수를 찾아보고 이를 BasicObject 까지가는 과정이다.
( ancestors chain 에서는 include 된 module 에서도 함수를 찾는 과정이 있다. 이는 아래에서 설명할 예정이다. )
즉 우리가 함수를 사용한다면 Ruby는 receiver로 부터 시작해서 해당 함수를 찾을때까지 ancestors chain을 수행한다.
이를 코드와 그림으로 예를 들어보면 다음과 같다.
class MyClass def hello puts 'MyClass hello instance method' end end class MySubClass < MyClass end my_sub_class = MySubClass.new my_sub_class.hello() # => MyClass hello instance method
다시한번 설명하자면 my_sub_class.hello( ) 를 실행하면 Ruby는 receiver 인
my_sub_class 에서 hello( ) 함수를 찾아보고 없을경우 오른쪽인 MySubClass로 이동한다.
MySubClass 에서 다시한번 hello( ) 함수를 찾아보고 없을경우 위로 올라간다.
MyClass에서 hello( ) 함수를 찾고 이를 실행한다.
(MyClass는 별도로 상속을 받지 않았기 때문에 기본적으로 Object를 상속받는다.)
이러한 과정을 "one step to the right, then up" 라고 부른다.
MySubClass의 ancestors chain을 확인해보면 아래와 같다.
MySubClass.ancestors # => [MySubClass, MyClass, Object, Kernel, BasicObject]
여기서 처음보는 녀석인 Kernel이 있다! 여기에 있는 Kernel은 Class가 아닌 Module 이다.
Module 과 Lookup
우리는 방금 ancestors chain이 클래스 로부터 superclass 방향으로 진행한다고 이해했다.
하지만 실은 include 된 모듈(Module) 들도 ancestors chain 에 대상에 들어간다.
Ruby는 모듈을 클래스 혹은 다른 모듈에 include 했을때 해당 모듈을 ancestors chain에 넣는다.
예를들면 아래와 같다.
module MyModule def hello 'MyModule hello instance method' end end class MyClass include MyModule end class MySubClass < MyClass end MySubClass.ancestors # =>[MySubClass, MyClass, MyModule, Object, Kernel, BasicObject]
Ruby 2.0 에서부터는 모듈을 insert 하는 다른방법인 prepend 가 추가되었다.
prepend는 include와 비슷하게 동작하지만 prepend 하는 모듈 혹은 클래스 보다 ancestors chain 의 앞에 들어간다.
다시한번 예를 들어보겠다.
module MyModule def hello 'MyModule hello instance method' end end class MyClass prepend MyModule end class MySubClass < MyClass end MySubClass.ancestors # =>[MySubClass, MyModule, MyClass, Object, Kernel, BasicObject]
그림으로 정리해보면 아래와 같다.
다중 include
그렇다면 include를 다중으로 사용하면 ancestors chain 은 어떻게 될까?
코드로 확인해 보자.
module Module0 end module Module1 end module Module2 include Module1 include Module0 end module Module3 prepend Module1 include Module2 end Module3.ancestors # => [Module1, Module3, Module2, Module0]
위의 코드에서 보는거와 같이 Module3는 Module1을 prepend 했고 그 후 Module2를 include 했다.
그런데 Module2 또한 Module1을 include 했다 그러면 Module1은 어떻게 되는걸까? 이때 Module3의 ancestors chain 에는
Module1이 이미 prepend로 들어가 있기때문에 Module2에서의 Module1 include는 무시된다.
Ruby는 include, prepend를 수행할때 ancestors chain에 해당 Module이 존재할경우 무시한다.
Kernel
Ruby는 print와 같이 우리의 코드 어디에서나 사용할 수 있는 함수들을 가지고 있다. 그리고 이러한 함수들은 실제로
우리코드 어느곳에서나 사용하고 있다.
이렇게 print와 같이 어디서나 사용되는 함수들은 사실 Kernel 의 private_instance_method 이다.
Kernel.private_instance_methods.grep(/^pr/) # => [:printf, :print, :proc]
그러면 Kernel의 함수가 어떠한 원리로 모든 코드에서 사용이 가능한걸까?
Object 는 Kernel을 include 하고있고 모든 클래스들은 Object를 상속받고 있기때문에
모든 클래스는 Kernel을 ancestors chain에 포함하고 있다!!
함수 실행
Ruby에서 함수를 실행하면 아까 위에서 설명했듯이 2가지의 작업을 실행한다.
- 함수를 receiver와 ancestors chain을 이용해서 찾는다.
- 함수를 실행한다.
이번에는 2. 함수의 실행에 대해서 알아보자.
우리가 Ruby interpreter 입장에서 생각해 봤을때 아래와 같은 코드가 있다 치자.
def hello temp = @v+1 other_method(temp) end
그리고 누군가가 hello 함수를 실행했을때 interpreter인 우리는 2가지 질문에 답해야 한다.
- @v 는 어떤 객체의 instance_variable 인가?
- 어떤 객체의 other_method(temp) 함수를 호출해야 하는가?
위의 2가지 질문에 대한 대답은 모두 receiver 이다. Ruby는 함수를 실행할때 receiver 에 대한 reference를 기억해 놓는다.
아래와 같은 코드가 있을때 결과가 어떨지 한번 예상해보자.
module Printable def print puts 'Printable print' end def prepare_cover puts 'Printable prepare_cover' end end module Document def print_to_screen prepare_cover # => self.prepare_cover 와 동일하다. format_for_screen # => self.format_for_screen 과 동일하다. puts "#{self.object_id}" print # => self.print 와 동일하다. end def format_for_screen puts 'Document format_for_screen' end def print puts 'Document print' end end class Book include Document include Printable end book = Book.new book.object_id # => 2494200 book.print_to_screen
결과는 다음와 같다.
# => Printable prepare_cover # => Document format_for_screen # => 2494200 # => Printable print
Document 에 정의되어있는 print_to_screen( ) 의 함수내부에서 print( ) 함수를 호출 할때
Document 에 정의되어있는 print( ) 를 호출하지 않은 이유는 무엇일까?
그 이유는 Book 클래스의 include 순서때문이다.
지금 Book클래스의 ancestors chain을 확인해보면 아래와 같다.
Book.ancestors # => [Book, Printable, Document, Object, Kernel, BasicObject]
위와 같이 Printable이 Document보다 ancestors chain 이 앞에 있기 때문에
book.print_to_screen 함수를 호출하면 ancestors chain을 타고 Document의 print_to_screen 함수를 실행하게 되고
print_to_screen 함수내의 함수들은 다시 self 기준으로 ancestors chain을 실행하는데
지금 self 는 receiver 인 book 객체이므로 Printable의 print 함수가 호출된다!
self 키워드
Ruby의 객체 안에서 실행되는 모든 코드는 self 라는 키워드를 통해서 Current Object 에 접근할 수 있다.
아래의 코드를 보자.
class MyClass def self_test @var = 20 # An instance variable of self my_method() # Same as self.my_method() self end def my_method @var = @var + 1 end end my_class = MyClass.new my_class.self_test # => <MyClass:0x00000002719948 @var="21">
우리가 self_test 함수를 호출하면 self 는 receiver인 my_class 가 된다. 그래서 @var 는 my_class 의 instance variable 이고
my_method( ) 를 실행할때 self 는 여전히 my_class 이기때문에 my_class.my_method( ) 와 동일하다.
특별한 2가지 경우를 제외하고는 self는 항상 마지막 함수 호출의 Object 이다.
Top Level
우리는 어떤 객체의 함수를 호출할때 해당 객체가 self 가 된다는것을 확인했다.
그렇다면 아직 아무 함수도 호출하지 않았을때의 self 는 무엇일까? 아래의 코드로 확인해보자.
self # => main self.class # => Object
Ruby를 실행하면 Ruby Interpreter 가 우리를 위해 main 이라는 이름의 객체를 설정해 놓는다.
이 main 이라는 객체는 call stack에 가장 위에있기 때문에 top-level context 라고 불린다.
클래스정의 에서의 self
Ruby에서 클래스 와 모듈을 정의하는 중에 사용하는 self 는 클래스 나 모듈 자신을 뜻한다.
class MyClass self # => MyClass end
지금은 당연하고 큰 의미 없어 보이는 이 개념은 나중에 이 책에서 소개하는 메인 개념이 될것이다.
오늘 알아본 내용은 정리하자면 다음과 같다.
- 함수를 호출하면 함수를 찾는 Method Lookup 과 함수를 실행하는 과정을 진행한다.
- Method Lookup 은 ancestors chain에 따라 순서대로 검사하며 이를 "one step to the right, then up" 라고 부른다.
- ancestors chain 은 Module 도 넣을수 있는데 Module을 넣기위해서는 include 와 prepend 가 가능하다.
- 이미 ancestors chain 에 포함된 Module 이나 Class를 넣으려고하면 무시한다.
오늘은 여기까지!
누군가에게 도움이 되었길 바라면서 오늘의 포스팅 끝~
'Ruby' 카테고리의 다른 글
[Ruby] 루비 메타프로그래밍(4) - Blocks, Scopes (0) | 2018.07.03 |
---|---|
[Ruby] 루비 메타프로그래밍(3) - Method (0) | 2018.06.30 |
[Ruby] 루비 메타프로그래밍(1) - Object Model (2) | 2018.06.16 |
[Ruby] View에 Decorator 패턴 적용하기 (2) | 2018.05.07 |
Ruby의 Symbols과 String의 차이 (0) | 2018.04.21 |
- Total
- Today
- Yesterday
- 루비 상수
- 인덱스
- InnoDB
- ruby
- MySQL
- MySQL 인덱스
- mysql lock
- autoload_paths
- db
- 루비
- lock
- MySQL 족보
- dead lock
- 엘라스틱서치 기초
- gap lock
- 되추적
- 메타프로그래밍
- 넥스트 키 락
- 루비 메타프로그래밍
- 트랜잭션
- innoDB lock
- ruby meta programming
- 페어프로그래밍
- Elasticsearch Cluster
- Pair-programming
- next key lock
- 갭 락
- metaprogramming
- 페어 프로그래밍
- Autoloading
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |