객체의 실행과 전달되는 메소드의 처리과정을 살펴보기

이전에 올렸던 내용을 좀 더 떼어내서 살펴보겠습니다.
(원래는 메세지 전달에 대한 궁금증 뿐이었습니다만.. 이건 심화학습정도 되겠네요)

이 문제와 관련되어 메일링 리스트에서 왔다갔다한 내용은 다음과 같습니다

스몰토크는 메소드 단위로 컴파일됩니다. 웍스페이스에서 선택해서 DoIt을 한다고 해도
해당 텍스트를 마치 메소드인냥 컴파일한다음 실행합니다. 
실행하는 메소드는 ProtoObject>>withArg:excute: 인가 그렇고
두번째 인자가 컴파일된 메소드입니다. 컴파일 결과는 스택기반 가상기계용 바이트코드구요.
=============================================
그럼 좀 구체적으로 질문을 드려도 될까요?

Transcript show: 'a'가 compile된다면

ProtoObject withArg:'Transcript show: 'a''
이런식으로 된다는 의미인건가요?
compile이 끝나면 vm위의 메모리에 바이트코드 형태로 존재하게 될거라는건
예상이 되는데....
실제로 어떻게 동작되는지 조금 궁금합니다.
=============================================
Transcript show: 가 어떤식의 VM 머신코드로 컴파일 되는지는 다음처럼 해보면 알 수 있습니다.

"Transcript 가 ThreadSafeTranscript의 인스턴스이므로.."

(ThreadSafeTranscript >> #show:) symbolic. 
"print it 해보세요."

관련해서 더 자세한 내용은 다음 페이지를 참고해보면 도움이 되겠네요.

Introduction to Smalltalk bytecodes

어? 그럼 이떤식으로 메세지가 compile되는지도 알 수 있다는거여?
일단 까라면 까야하는법 PrintIt을 해봤습니다. 그랬더니…

'21 <70> self
22 <88> dup
23 <10> pushTemp: 0
24  send: print:
25 <87> pop
26  send: endEntry
27 <87> pop
28 <78> returnSelf
'

이런결과를 workspace에서 볼 수 있네요.
……………………………
뭔가 죽이는데요? 저 내용 자체가 Smalltalk VM에서 인식하는 수준의 lowlevel이라는건데…
또 다른 방법이 있는거같습니다.
이번에는 한주영님이 알려주신 내용을 보겠습니다.

Transcript show: 'a'를 선택해서 Debug it 해보시면
SmalltalkEditor 클래스의 다음 메소드에서 멈춰있습니다.

debug: aCompiledMethod receiver: anObject in: evalContext

여기, aCompiledMethod 가 UndefinedObject>>#DoIt 메소드이며
실제로는 CompiledMethod 인스턴스(CompiledMethod는 ByteArray를 상속)입니다.
아마 임시로 생성된 메소드라고 보시면 될것 같아요.
receiver(anObject)는 nil 입니다.

DoIt 메소드의 몸체가 바로 "Transcript show: 'a'"입니다.
DoIt 이라는 CompiledMethod는 다음과 같이 바이트코드를 가지고 있습니다.

25 <41> pushLit: Transcript
26 <22> pushConstant: 'a'
27  send: show:
28 <7C> returnTop

메모리 상에 35바이트를 차지하네요.

debug:receiver:in: 에서 다음과 같이 호출합니다.

   aCompiledMethod
                       valueWithReceiver: anObject
                       arguments: (evalContext ifNil: [ #() ] ifNotNil: [ { evalContext } ])

valueWithReceiver: aReceiver arguments: anArray
  ^ aReceiver withArgs: anArray executeMethod: self

aReceiver는 nil이지만 withArgs:executeMethod:를 호출하는데에는 아무 문제가 없죠 ^^

#Transcript 심볼을 푸시
'a' 상수를 푸시
#show: 심볼을 'send'
그리고 리턴이네요..

'send'를 처리하고나면 결과값이 stack top에 저장되어 있을 거고요.

실제 본인이 디버깅 해가면서 추적하는 방법을 통째로 알려주셨습니다.
사실 방법이야 여러가지가 있겠습니다만…
중요한건 실시간으로 컴파일되는 바이트코드를 결과물로 봐가면서 디버깅을 할 수 있고
그걸로 내부 구조를 좀 더 볼 수 있다는게 중요한거같네요.

아직 이 내용을 제대로 다 숙지하지는 못했습니다만…..
좀 더 코드를 두들겨보고 스스로 결론을 낼 수 있는 단계가 오면
최종과정으로 한번 더 글을 쓰도록 하겠습니다 😀

Smalltalk에서 메세지에 대한 추가생각

Transcript show: 'aaaa'

Smalltalk 구문인 위의 구문 에서 show: ‘aaaa’ 부분이 통째로 메세지인데는 이유가 있는것 같습니다.

http://trans.onionmixer.net/mediawiki/index.php?title=SqueakByExample:5.8

위 주소의 요약부분중 다음과같은 부분이 있습니다.

모든 것은 메시지 전송으로 이루어집니다. 스몰토크에서는 "메서드 호출"을 하지 않고, "메시지 전송"을 사용합니다.
메세지가 전송된다음, 수신자는 그 메시지에 응답하기 위해 수신자 자신의 메서드를 선택합니다.

어?…………..이거 이상하다..
저는 여태까지 Smalltalk에 대해서 잘못 생각하고 있었던거같습니다….

Transcript에 있는 show라는 셀렉터를 쓰는게 아니었던거네요.

그냥 Transcript라는 인스턴스 객체에 show: ‘aaaa’ 라는 메세지를 보내는거같습니다.
Transcript에 show:가 있던없던 일단 던지고 보는거죠.
없으면 없다고 에러를 낼거고 있으면 찾아서 동작할거고….

이게 사실 발상에서 큰 차이를 가져오는게
보내는쪽은 Transcript에 대한 정확한 정보없이 그냥 보내는거란 말입니다.
받는쪽은 보내는쪽의 메세지에서 show를 추출해서 내부에 해당되는 method가 있는지 찾는거구요.

Form1.OnClick(TObject);

위의 경우처럼 대부분의 언어에서는 인수를 넣을때 그것을 소화하는 객체에서 지원하는 메소드를 다 알고있고
그것을 정확하게 지시해야하는데…
Smalltalk에서는 보내는놈도 그냥 보내는거고.. 받는놈도 그냥 받아서 매번 그냥 처리하는… 그런식이라는거죠.

사실 어차피 셀렉터 없으면 동작 안하고 에러나니깐 의미가 없다고 생각할지 모르곘는데
이 경우는 설계의 패러다임에서 좀 더 많은 자유도를 가져갈 수 있는
특성이 아닌가 합니다.

===================================================================
2013년 3월 6일 추가

한주영님::
“컴파일 중에는 메시지의 수신자를 따지지 않는다” 라고 하면 좀더 이해하기 수월할 것 같아요. 그리고 이 특징은 동적 타입 언어들에 공통된 것이고요.

이것이 설계적으로 조금 더 유연하면서 동시에 런타임에서 불안한 측면이 있지 않을까요? 그래서 단위테스트나 TDD같은 활동이 동적 언어에서 더 발전했다고 보는 견해도 있더군요. (더 쉬워서, 더 필요해서)

좀 찾아보니 수신자가 메시지를 처리할 수 없을 때의 처리도 제각각이네요. 스몰토크는 doesNotUnderstand(직계 후손이라 할 수 있는 루비는 method_missing)이 호출되며 기본 동작은 예외를 일으킵니다. 흉내쟁이 Objective-C는 아무 동작도 안하네요.

참고로 파싱된 MessageNode 는 receiver, selector, arguments 정보를 담고 있습니다.

스몰토크는 메소드 단위로 컴파일됩니다. 웍스페이스에서 선택해서 DoIt을 한다고 해도 해당 텍스트를 마치 메소드인냥 컴파일한다음 실행합니다. 실행하는 메소드는 ProtoObject>>withArg:excute: 인가 그렇고 두번째 인자가 컴파일된 메소드입니다. 컴파일 결과는 스택기반 가상기계용 바이트코드구요.

===================================================================
2013년 3월 6일 추가

김승범님::
스몰토크라는 시스템이 ‘메시지 전달’이라는 간단한 기본 원칙을 지켜가면서 설계되었기 때문이라 보시면 됩니다. 그리고 메시지 전달과 관련해서는 late-binding이 이뤄지는 것인데요, 이와 관련해서 The Early History of Smalltalk에는 다음과 같은 구절이 있습니다.

OOP is a late binding strategy for many things and all of them together hold off fragility and size explosion much longer than the older methodologies.

OOP라는 개념이 정립이 되고 시스템이 설계된 것이 아니라, 그 시대 수 많은 영감을 줬던 아이디어들을 배경으로 몇 가지 기본 원칙을 지켜가면서 시스템을 디자인한 결과 그 진화의 과정에서 OOP가 탄생이 되었고요, late-binding이 그 중에 중요한 전략 중 하나란 것이죠.

다음 글도 읽어보면 양파님이 매우 재밌어 하실거 같네요. 🙂

http://worrydream.com/EarlyHistoryOfSmalltalk/

‘Inventing on Principle’ 동영상으로 수 많은 파문(?)을 일으킨 Bret Victor가 최근에 VPRI와 협업을 해서였는지는 모르지만, 자신의 홈페이지에 문서를 읽기 좋게 다시 깔끔하게 정리해서 올려두었습니다.

Smalltalk에서의 Collection

http://trans.onionmixer.net/mediawiki/index.php?title=SqueakByExample:3.6

이 내용을 보면 Collection에 대한 부분이 나오게 됩니다.
사실 중요한건 Collection이 아니라 collection이 가지고 있는 성질에 대한 부분인데요..
번역문에 내용을 추가할까 하다가.. 이건 아니다싶어 따로 blog에 정리를 하도록 하겠습니다.

Smalltalk에서의 Collection이라는것은 일반적으로 STL에 많이 비교가 되더군요.
중요한건 C++에서의 STL은 “자료를 다루기위한 표준라이브러리” 라는겁니다.

C++ 표준 템플릿 라이브러리, 즉 STL은 C++프로그래밍에서 최상의 성능을 얻어내는 것,
그리고 서로 다른 자료구조와 알고리즘을 결합하기 위해 설계되었습니다.

참고URL::http://t3ddy.tistory.com/15

Smalltalk에서의 Collection을 이런 STL같은거다..라는 개념적인 비교가 아니라
여기서 주요하게 비교되는점인 Iterator에 대해 좀 더 알아보도록 하겠습니다.

사실 C++에서 STL의 장점은 Iterator(반복처리자)를 통한 데이터의 탐색 및 조작에 있습니다.

C++에서 Iterator를 통한 데이터의 처리에 대한 간단한 예를 보겠습니다.
예제출처::http://printf.egloos.com/1992692

#include 
#include 
using namespace std;

void main( )
{
    vector v;

    v.push_back(10);
    v.push_back(20);
    v.push_back(30);

    vector::iterator iter;
    for(iter = v.begin() ; iter != v.end() ; iter++)
        cout < < *iter << endl;

    iter = v.begin()+2;
    cout << *iter << endl;
}

위의 예제에서 vector라는 템플릿으로 v를 선언했고 값을 넣었습니다. 그리고 생성된 v 라는 인스턴스에 값들을 집어넣게되죠.
(물론 vector는 int형으로 선언되었기때문에 v에 넣을 수 있는값은 int입니다)
그리고 vector의 Iterator타입으로 iter라는 변수를 선언합니다.
이후 iter를 통해서 v라는 int-vector타입의 인스턴스의 데이터를 탐색합니다. 그리고 값을 출력하죠.

이렇게 Iterator는 탐색이 가능한 데이터집합의 포인터로서 사용될 수 있습니다.

굳이 비교를 하자면 php에서 database에 질의를 하고 그 결과를 변수에 받은다음 data를 seek하는데 이것도 일종의 Iterator라고 할 수 있겠죠.
(정확한 설명은 아닐 수 있겠습니다만.. 개념적으로는 맞지 않을까 합니다)

Smalltalk에서 Collection은 이러한 Iterator를 사용해서 Collection안의 데이터를 다루기 쉽게 해준다는데 있습니다.
맨위의 URL에 있는 예제를 아래에 옮겨서 보도록 하죠.

result := String new.
(1 to: 10) do: [:n | result := result, n printString, ' '].
(1 to: 10) collect: [ :each | each * each ]

이 2개의 예제를 보면 :n 그리고 :each가 Iterator인걸 알 수 있습니다.

첫번째 예제에서 do:라는 셀렉터에 [ ] 으로 묶여있는 블록식 안에서 :n은 n이라는 1 에서 10까지의 증가되고있는 현재값을 취급합니다.
1에서 10까지의 현재의 pointer가 되겠네요.

마찬가지로 두번째의 예제에서는 1에서 10까지의 증가되는값중 현재값을 :each가 받아서 데이터의 포인터로 사용하고 있습니다.

이렇게 Iterator는 현재 다루고있는 어떤타입의 배열에서도 데이터의 포인터값으로 사용될 수 있습니다.

실제로 Object Browser에서 Collections>Collection 을 보면 Collection을 상속받은
Array, FloatArray, Matrix, String, WordArray등을 찾을수가 있는데
이는 Smalltalk에서 사용되는 대부분의 배열은 Collection에서 지원하는 Iterator를 쓸 수 있다는것을 의미합니다.

한마디로 간단하게 설명되기는 애매해서 별도로 설명을 좀 길게 늘였지만
이것으로 제대로된 설명이 되는지는 모르겠습니다......-.-;