ruby-dev 소식: 클래스 로컬 인스턴스 변수표기법
Posted by 미스란디르
얼마전에 사사다 코이치씨가 1.9(trunk) 에서 singleton.rb를 사용할 경우 에러가 난다는 포스팅을 했었다. ruby-dev:30263
요녀석은 make test-all을 하면 볼 수 있고, 저 포스팅의 예제코드를 돌려봐도 마찬가지로 볼 수 있다. (현재시각의 trunk에는 workaround처리가 되어 있음)
이 에러는 r11630 에서 발생한다. 무엇인고 하면, @가 인스턴스 변수, @@가 클래스 변수인데, @_이 클래스 로컬 인스턴스 변수를 뜻하는 prefix가 되면서 부터다.
class A
def m
@var = "hi" # regular instance variable
@_var = "hello" # class-local instance variable
end
end
class B < A
def n
puts @var.inspect # same as @var, above
puts @_var.inspect # not same as @_var above
end
end
o = B.new
o.m
o.n
이 코드의 예상 결과는? 주석을 참조한다면 어떻게 될지 짐작이 간다. 결과는 다음과 같다
"hi"
nil
내부적으로 어떻게 동작할까? 당신이라면? 나는 코드를 미리 봐버려서 짐작할 기회가 없었다. 답은 심볼 이름뒤에다 클래스를 붙여버린 것이다. 예를들자면 위의 예에서는 @_var라면 :_var/A가 된다. 이러면 B의 객체의 scope에서는 @_var를 접근할 때 :_var/B 를 찾게 되지만, 이녀석은 당연히 없으니 @_var는 값이 들어간적이 없는 인스턴스 변수, 즉 nil이 된다.
@__mutex__를 사용해서 싱클튼을 구현하고 있다. (동시 접근을 막기 위해). 일단 싱글튼 코드를 먼저 살펴보자.
class << Singleton
def __init__(klass)
klass.instance_eval {
@__instance__ = nil
@__mutex__ = Mutex.new
}
def klass.instance
return @__instance__ if @__instance__
@__mutex__.synchronize {
return @__instance__ if @__instance__
@__instance__ = new()
}
@__instance__
end
klass
end
def inclued(klass)
Singleton.__init__(klass)
end
end
사실은 included에 코드가 몇줄 더 있지만 귀찮으니까 뺐다. included는 모듈이 클래스에 믹스인 될때 호출되는 메서드(말하자면 콜백)이다. 그리고 형식인수로 그 포함한 클래스를 받는다. 그리고 그것을 Singleton.init으로 넘기고, instance_eval을 사용해서 그 클래스의 scope에서 @mutex 를 초기화한다. 그리고 겸사겸사 klass.instance 메서드(이건 말하자면 클래스 메서드) 도 정의하는데, 이때 지금 초기화한 @mutex 를 사용한다.
뭐 보기만 해서는 전혀 문제가 없어보인다.
여기서 잠깐 다른 코드를 보고 넘어가자.
class Merong
def m
@__y__ = "Yes!"
end
attr_accessor :__x__
end
m = Merong.new
m.m
m.instance_eval { @__x__ = "Yes!" }
puts m.instance_variables.inspect
위에서 말한대로라면, @x__/Merong @y__/Merong 이 보여야 한다. 그러나!
[:@__x__/#<Class:#<Merong:0xb7ec4390>>, :@__y__/Merong]
문제는 요것. instance_eval을 사용할 경우 좀 애매하게 다른 결과가 나온다.
따라서 Singleton의 경우도 그럴거라고 예상할 수 있다. 귀찮아서 실제로 보는건 생략.
현재는 이것과 관련해서 _ 를 계속 쓸 것인지, 아니면 (v) 라던가 기타 다른 형식을 쓸 것인지를 고민하고 있다. (마츠씨와 사사다씨가)
================
사실 이 포스팅을 쓰면서, @__mutex__ 에 대입하는 부분과 사용하는 부분이 scope가 다르기 때문일꺼라고 막연하게 짐작했는데, 예상치 못한 결과가 나왔다. 아마도 ivar2관련 버그이리라.
