AndreaSmalltalkLecture:SmalltalkLecture 02

From 흡혈양파의 인터넷工房
Jump to navigation Jump to search
2. 객체들과 나누는 이야기

객체들과 나누는 이야기

앞 가름[章]에서 우리는 Smalltalk 환경에 대한 아주 기본적인 것을 알아보았습니다. Smalltalk 환경을 설치하고 유지하는 방법에서부터, Smalltalk에서 명령을 실행시키는 방법, 그리고 Smalltalk 프로그램이 도대체 어떤 모양을 하고 있으며 이것을 어떤 방법으로 실행시키는지에 대해서 우리는 개괄적으로 살펴본 셈입니다. 또한 Smalltalk에는 대략 어떤 종류의 창들이 있으며, 이 창이 각각 무엇을 하는지에 대해서도 살펴보았습니다.


이제 이번 가름에서부터 지금까지 우리들이 공부한 기본적인 것들을 바탕으로 본격적인 Smalltalk 공부에 들어가게 될 것입니다. 우선 객체란 무엇인지에 대해서 살펴보고, 이어서 Smalltalk에 이미 있는 여러 가지의 객체에 대해서 살펴볼 것입니다. Smalltalk에 이미 존재하는 객체들에는 어떤 것들이 있으며 그들은 어떻게 생겼고, 또한 그것들이 하는 일이 무엇이며, 또한 우리는 그 객체들을 가지고 무엇을 할 수 있는지에 대해서 알아볼 것입니다.


이 가름을 마칠 때쯤이면 여러분은 Smalltalk에서 객체를 다루는 아주 기본적인 방법에 대해서 익숙해져 있을 것입니다.


객체?

흔히 Smalltalk를 일러 "객체 지향 프로그래밍 언어"(OOPL: Object Oriented Programming Language)라고 부릅니다. 이 글을 읽는 여러분들 중에는 꼭 프로그래밍을 공부하지 않더라고 컴퓨터를 사용하면서 "객체 지향"이란 말은 한 번쯤 들어본 분들이 많을 거라고 생각됩니다. 솔직히 요즘 많이 쓰이는 "멀티미디어"(multimedia)란 낱말이 유행(?)하기 이전에는 "객체 지향"이란 낱말이 유행했었습니다. "객체 지향 데이터베이스", "객체 지향 운영 체계", "객체지향 분석 및 설계", "객체지향 소프트웨어 공학", "객체지향 동호회" 등등... 요즘은 그 유행의 거품이 거두어지고 슬슬 객체지향의 본연의 모습을 연구하려는 분위기로 가고 있는 것 같습니다.


어쨌든 Smalltalk가 객체 지향 프로그래밍 언어라면, 도대체 "객체지향이란 무엇인가"에 대해서 한 번쯤은 생각을 해 봐야 할 것입니다. 그리고 객체지향이런 무엇인가를 생각하려면 먼저 '객체란 무엇인가'라는 근본적인 물음에 대해서 어느 정도의 가닥을 잡고 있어야 할 필요가 있습니다. 일단 객체지향이라는 것도 알고 보면, 모든 것을 객체 위주로 생각하는 것이기 때문에, 객체가 무엇이며 그것이 어떤 성질을 가지고 있는지, 또 객체는 어떻게 다루면 되는 것인지에 대한 것을 실제로 부딪히면서 채득하는 것이 좀 더 쉬운 접근방법이라고 생각됩니다. 자전거를 배우려면 직접 타봐야 하듯이 말입니다. 이제 그러한 마음가짐으로 이제부터는 객체가 무엇인지부터 하나씩 짚어보도록 하겠습니다.


'객체 지향 프로그래밍'이 컴퓨터에서 이루어진다고 해서, '객체'라는 개념 자체가 컴퓨터에만 한정되어 있는 것은 결코 아닙니다. 오히려 객체는 평범한 우리의 일상 생활에서 그 개념을 잡아야 합니다. 왜냐하면 객체지향이라는 생각 자체가 우리가 살고 있는 세상에서 일어나는 문제를 컴퓨터로 해결하기 위해서 대두되었기 때문입니다.따라서 지금부터는 프로그래밍에 대한 생각을 머리에서 잠시 몰아 내고, 편안한 마음으로 글을 읽어 나가십시오.


여러분은 지금 제가 쓴 글을 보고 있습니다. 무엇을 통해 보고 계십니까? 대부분 모니터를 통해 보실 것입니다. 가끔씩 이 글의 다음 쪽을 보기 위해서 여러분은 무엇을 합니까? 대부분 글쇠판이나 마우스를 누를 것입니다. 글쇠판과 마우스는 무슨 일을 할까요? 여러분의 생각을 컴퓨터로 전하는 일을 합니다. 모니터는 어떻습니까? 끊임없이 여러분에게 글과 그림을 뿌려 데고 있습니다. 컴퓨터가 만들어 놓은 결과를 여러분에게 전달하기 위해서일 것입니다.


여러분은 자동판매기를 이용해 본 적이 있습니까? 그럼 자동판매기에서 어떻게 원하는 음료수를 뽑아 내는지를 가만히 기억해 보십시오.


우선 동전을 필요한 만큼 넣습니다.
그런 다음 원하는 음료수(저는 '커피')의 이름이 적힌 단추를 누릅니다.
그러면? 음료수('커피'?)가 나옵니다.
여러분은 문을 열고 음료수를 꺼낸 다음 마시면 됩니다.


여러분은 개를 기르고 있습니까? 개의 종(種)은 무엇입니까? 털 색깔은 어떻지요? 몸무게는 어떻게 될까요? 뭐, 제가 이런 것들을 알 필요는 없겠지만, 여하튼 개는 개 나름으로의 특성을 가지고 있습니다. 같은 개라 하더라도... 여러분이 길다란 막대기 하나를 던져 주고 "주워 와!"하면 개는 그 지시를 받아서 행동합니다. 개가 똑똑하면 여러분의 지시를 제대로 수행할 것이고, 그렇지 않으면 엉뚱한 일을 수행할 것입니다. 훈련하기에 달린 거죠. :)


아프면 여러분은 병원에 갑니다. 병원에 가서 여러분은 의사에게 병을 고쳐 줄 것을 부탁합니다. 그러면 의사는 여러분의 상태를 파악하고 진찰한 후 적절한 처방을 써서 여러분의 병을 고쳐 줍니다. 여러분이 병원에 갈 때 '인체 해부학'이나 '생리학'과 같은 의학을 알 필요가 있었습니까? 그저 여러분은 의사에게 가서 진찰을 부탁했을 뿐입니다. 그것으로 족합니다. 여러분이 만일 의사가 되고 싶으면 의학 공부를 하면 됩니다. 허나 모든 사람이 아플 때마다 의사가 될 수는 없지않겠습니까? 내가 필요하면 병원에 가서 의사에게 진찰을 부탁하면 되는 것입니다.


객체(object)를 가장 간단한 낱말로 표현한다면 '물건'이 될 것입니다. 넓은 의미에서 살아 있는 생물도 물건에 속합니다. 생물(生物)을 한자로 풀어쓰면 "살아 있는 물건"이란 뜻을 가지고 있습니다. 지금까지 제가 앞에서 예로 든 모든 것이 객체입니다. 모니터, 글쇠판, 마우스, 자동판매기, 음료수, 동전, 문, 좀 더 생각을 넓혀서 살아 있는 개, 심지어는 '의사'와 같은 사람까지도, 이 모든 것이 '객체'입니다.


물건은 어느 하나 쓸모가 없는 곳이 없습니다. 모든 것은 자기 나름의 역할을 가지고 존재합니다. 심지어 필요 없다고 생각되는 쓰레기들도 모두 자신의 소명을 다하고 있습니다. 다시 말하면, 모든 객체는 자신의 역할을 가지고 있다고 볼 수 있습니다. 객체가 자신의 역할을 충실히 수행함으로써 세상이 제대로 돌아가고 있는 것입니다. 이 세상은 참으로 많은 객체로 가득 차있는데, 각각의 객체들이 저마다 자신의 역할을 충실히 수행해 나간다고 생각해 봅시다. 일사불란하게 움직이는 자연을 보면, 경이롭기까지 합니다. 요컨대, 객체는 저마다의 역할을 가지고 있습니다.


객체가 어떤 역할을 수행하기 위해서는, 누군가가 그 객체에게 무언가의 __지시를 내려 주어야__ 합니다. 글쇠판이 컴퓨터에게 정보를 입력하기 위해서는 누군가가 글쇠판을 __눌러 주어야__ 합니다. 즉 손으로 글쇠판에게 "누른다"라는 지시를 내리는 것입니다. 모니터는 컴퓨터의 중앙처리장치(CPU)의 지시를 받아 움직이는 그림판(graphic card)의 지시로 화면에 여러 가지 정보를 뿌립니다. 자동판매기는 말할 것도 없습니다. 우리가 동전을 넣고 음료수를 마시겠다고 지시하는 것입니다. 그 결과 자동판매기는 음료수를 내놓는 것이지요. 개는 우리의 말을 어느 정도는 알아듣습니다. 우리의 말을 알아들을 수 없는 객체에게 우리는 물리력(物理力)으로 지시를 내립니다. 그러나 말을 알아들을 수 있는 객체에게, 우리는 '언어'를 통해 지시를 내립니다. 병원의 의사는 말할 것도 없습니다. 같은 사람의 입장에서 '지시를 내린다'는 표현을 쓰기가 좋지 않으므로, '치료를 부탁한다'라고 말만 바꾼 것뿐이지, 실재로는 우리가 '의사'라는 __객체에게__ "치료해 달라"고 지시하는 것과 다를 바가 없습니다. 이와 같이 모든 객체는 지시에 의해 움직입니다. 다시 말하면, 우리가 어떤 객체를 사용하기 위해서는, 반드시 그 객체에게 지시를 내려 주어야만 한다는 것입니다. 객체는 지시를 받음으로써 어떤 일을 합니다.


객체에는 저마다의 특성이 있습니다. 개의 특성은 "다리가 네 개, 눈이 두 개, 입은 하나..." 등과 같이 열거할 수 있고, 마우스의 특성은 "손에 꼭 맞도록 설계되어 있고, 두 개 또는 세 개의 단추가 있다"쯤으로 말할 수 있을 것입니다. 객체의 역할은 그 객체가 가지고 있는 특성에 따라 좌우됩니다. '개'와 '의사'는 같은 생물이라 하더라도 가지고 있는 특성이 서로 다르기 때문에 자기들만의 역할이 있는 것입니다. 만일 의사와 개가 완전히 똑같은 특성을 가지고 있다면, 우리는 개에게 병을 치료하라고 지시할 수 있을 것인데, 두 객체의 특성이 다르기 때문에 감히 개에게 "병 고쳐 달라"는 사람이 없지 않습니까? :) 따라서 모든 객체는 자신들만의 특성이 있고, 이 특성이 결국 객체의 역할을 규정지어 줍니다.


그런데 여러 개의 객체를 다루다 보면 사로 비슷한 특성을 가진 객체들을 발견할 수 있습니다. 예를 들어 '글쇠판'과 '마우스'는 "우리의 생각을 컴퓨터에게 입력시킬 수 있는 특성"을 가지고 있습니다. 또한 '진돗개'나 '푸들'은 서로 다른 특성을 가지고 있는 객체이지만, '개'만이 가지고 있는 공통적인 특성을 가지고 있습니다.


이와 같이 서로 비슷한 특성을 가지고 있는 객체들은 따로 묶어서 갈래(class)를 지을 수 있습니다. '갈래를 짓는다'는 말은 종류별로 나눌 수 있 다는 말입니다. '마우스'와 '글쇠판'은 "입력 장치"라는 갈래로, '진돗개'와 '푸들'은 "개" 라는 갈래로, 다시 '개'와 '사람'은 "생물"로 갈래를 지을 수 있습니다. 그리고 '입력 장치', '개', '사람', '생물'은 모두 '물건' 이라는 갈래에 속하게 됩니다. 요컨대, 모든 객체는 저마다 자신이 속해 있는 갈래(class)가 있습니다.


좀 지루했습니까? 하지만 위의 글들이 흔히 말하는 '객체'의 특성을 대부분 설명해 주고 있습니다. 객체가 가지고 있는 몇 가지 성질들을 정리해 두겠습니다. 모두 지금까지 읽으신 글과 연결되어 있으니 곰곰이 생각해 보시기 바랍니다.

  • 객체는 저마다 맡은 역할이 있다.
  • 객체는 지시를 받음으로써 자신의 역할을 수행한다.
  • 객체는 나름으로의 특성을 가지고 있다.
    • 이 특성이 객체의 역할을 규정짓는다.
  • 비슷한 특성을 가진 객체들을 모아 '갈래'를 지을 수 있다.
    • 모든 객체는 저마다 자신이 속한 갈래가 있다.


조금 정리가 되셨습니까? 아직도 좀 애매하다고 생각하실 지 모르겠습니다. 그렇다고 이것을 모두 외우려고 하지는 마십시오. 중요한 것은 위의 사실들을 무조건 외우는 것이 아니라, 몸으로 느끼는 것입니다.


이제 바로 다음 마디[節]에서 우리는 지금까지 공부한 객체에 대한 여러 가지 특성이 Smalltalk의 객체에도 적용되는지를 실제로 명령을 내려봄으로써 몸으로 느껴보게 될 것입니다.


몸으로 느끼기

앞의 마디에서 우리는 객체가 무엇인지에 대해서 대강의 가닥을 잡아보았습니다. 저는 여기서 한 개의 보기도 들지 않았습니다. 어땠습니까? 구름을 잡는 것과 같지 않았습니까? 이렇게 백 마디의 말보다는 한 마디의 실행이 진가를 발휘할 때가 있는 법입니다. 우리들이 전에 모르던 사실을 알고자 할 때 추상적인 개념 설명보다는 이 개념이 실제로 적용되는 과정을 몸으로 느낄 때, 그것이 머리 속으로 생각한 개념적인 것들보다 훨씬 오래 간다는 것을 알 수 있습니다. 이제 이 마디에서는 Smalltalk의 객체에 대해 여러 가지 명령을 실행시켜보고 그럼으로써 객체와 객체지향에 대한 개념을 몸으로 익혀보도록 합니다.

새 술은 새 부대에...


옛 속담에 "새 술은 새 부대에 담아라"는 말이 있습니다. 이 말이 Smalltalk 를 공부할 때에도 적용됩니다. Smalltalk에서 명령을 실행시키기 위해서는 기본적으로 일터(workspace)가 필요합니다. 그래서 여러분이 새로운 가름이나 마디, 그리고 새로운 나눔을 시작할 때에는 일터를 새로 열어서 작업을 하면 좋다는 것입니다. 그러면 나중에 여러분이 작업한 것을 저장하기도 편하고, 여러분이 실행했던 명령어들을 그대로 나누어서 볼 수 있기 때문에 매우 도움이 될 것입니다. 새로운 가름[章, 마디[節], 나눔[部]이 시작될 때에는 항상 새 일터에서 시작하도록 합시다.


글토막 가늠하기

앞의 "1.3"마디에서, 우리는 Smalltalk로 작성된 여러 가지의 간단한 명령을 실행시켜보았습니다. Smalltalk의 명령을 실행할 때, 몇 가지의 방법이 있었습니까? 기억나십니까? 네, 실행할 명령을 입력하고 덩이(block)를 씌운 후 Ctrl-D, Ctrl-E, 또는 Ctrl-I 중 하나의 글쇠를 눌러서 명령을 실행시켰습니다. 실제로 Smalltalk에서 명령을 실행하는 기본적인 방식은 방금 이야기한 세 가지가 있습니다. 그리고 각각의 쓰임새가 저마다 다릅니다. 이제 이 나눔[部]에서는 Smalltalk에서의 이러한 명령 실행에 대한 몇 가지 개념을 짚고 넘어가겠습니다. 몸으로 느끼기 위해서는 어떻게 느껴야 하는지를 알 필요가 있지 않겠습니까?


전통적으로 Smalltalk에서는 "명령을 실행한다"(execute the command)라는 말을 쓰지 않습니다. 대신 "글토막을 가늠한다"(evaluate the expression)라는 말을 사용합니다. 여기서 '글토막'이란 "Smalltalk 명령들로 이루어진 글의 조각"(the pieces of Smalltalk's code)라는 말입니다. 왜 Smalltalk에서 이러한 말을 쓰는지는 모르겠지만, 필자도 앞으로 Smalltalk의 관례를 따라 글을 쓰도록 하겠습니다. "로마에서는 로마법을!"이라는 말이 있듯이...


여하튼, Smalltalk에서 글토막을 가늠하는데는 아래와 같은 세 가지의 다른 방식이 있습니다.

  • 가늠하기(Evaluating): 말 그대로 "Smalltalk 명령을 실행한다"는 뜻입니다. 글토막을 가늠하면, 글토막에 쓰여진 Smalltalk 명령이 실행됩니다. 명령을 실행한 뒤에 나오는 결과는 무시해버립니다. 명령의 결과보다는 실행하는 것 자체가 중요할 때 사용하는 방식입니다. Workspace > Evaluate It 메뉴를 사용하거나 Ctrl-E 글쇠를 사용합니다.
  • 펴기(displaying): 글토막을 가늠하고, 가늠한 결과를 현재 일터에 덩이를 씌워 나타냅니다. 주로 명령을 실행한 뒤에 내놓는 결과가 중요할 때 사용합니다. 계산식 등을 가늠할 때 주로 사용되겠지요. Workspace > Display It 메뉴를 사용하거나 Ctrl-D 글쇠를 사용합니다.
  • 탐색하기(inspecting): 글토막을 가늠한 뒤에 생기는 결과값을 "객체 탐색기"(object inspector)를 통해서 보여줍니다. 명령을 실행한 후 내놓는 결과값이 매우 복잡한 구조를 하고 있을 때, 이를 좀 더 잘게 쪼개어 값을 관찰하고싶을 때 사용합니다. Workspace > Inspect It 메뉴를 사용하거나 Ctrl-I 글쇠를 사용합니다.


그럼 이제부터 보기를 통해 글토막을 가늠하는 방법들의 쓰임새를 알아보도록 하겠습니다.("1.3"마디를 주의 깊게 읽은 사람이라면 매우 쉽게 이해할 수 있을 것입니다.)


우선 다음의 글토막이 있습니다.

1 + 2.


보통 Smalltalk의 글토막을 가늠하기 위해서는 먼저 가늠할 글토막에 덩이를 씌워서 선택해야 합니다. 그런 다음 글토막을 가늠하도록 되어있습니다. 그런데 Dolphin Smalltalk에서는 한 가지 편리한 기능을 제공합니다. 그것은 바로 "한 줄 가늠" 기능입니다. 위의 글토막과 같이 한 줄로 이루어진 글토막은 덩이를 씌울 필요 없이 바로 가늠할 수 있다는 것입니다.

Dolphin Smalltalk가 아닌 다른 Smalltalk에서는 기본적으로 한 줄 가늠 기능을 제공하지 않습니다. Dolphin Smalltalk와 Smalltalk MT의 경우에만 한 줄 가늠 기능이 지원되는 것 같습니다.


자, 그럼 위의 글토막을 가늠해봅시다. Workspace > Evaluate It 메뉴를 고르던지, 글쇠판의 Ctrl-E 글쇠를 이용해서 위의 글토막을 가늠해 봅시다.

1 + 2.   "아무 결과도 안 나타남"


아무런 결과도 나타나지 않았습니다. 여기서 우리는 위의 글토막을 가늠하기만 했지, 그 내용을 펴지는 않았습니다. 그러므로 이와 같은 결과가 나타난 것은 당연한 것이지요.


이제 그럼 방금 가늠했던 글토막을 펴 봅시다. Workspace > Display It 메뉴를 사용하던가, 글쇠판에서 Ctrl-D 글쇠를 누르면 됩니다. 어떻게 되었습니까?

1 + 2.   3


"3"이라는 결과값이 덩이를 쓴 체 나타났습니다. 이와 같이 '펴기'는 글토막의 내용을 가늠한 후 그 결과로 남는 값을 화면에 뿌려줍니다. 이 경우에는 덩이를 쓴 "3"이라는 값이 바로 결과값인 것입니다.


마지막으로 이 글토막을 탐색해 봅시다. Workspace > Inspect it 메뉴를 사용하던가, Ctrl-I 글쇠를 사용하여 글토막을 탐색해 봅시다. 어떻게 되었습니까?

1 + 2.  "탐색기 나타남"


위의 글토막을 탐색하면 "Inspecting a SmallInteger" 라는 제목을 가진 탐색기가 나타날 것입니다. 왼쪽 창에는 "self"가, 오른쪽 창에는 "3"이 나타나 있을 것인데, 이것은 지금 우리는 "3"이라는 결과값을 탐색하고 있다는 의미입니다.


그러면 "1 + 2."의 경우는 가늠하기, 펴기, 탐색하기 중 어떤 방법이 가장 적당하겠습니까? 네, 이번 경우에는 펴기가 제일 적당합니다. 가늠하기의 경우는 아예 값이 나타나지를 않았기 때문에 사용할 수 없고, 탐색하기의 경우에는 "3"이라는 결과값을 얻기 위하여 탐색기를 쓰는 것이 되는 것이므로, 결국은 결과값을 덩이에 씌워 일터에 뿌려주는 펴기가 알맞은 방법이 되는 것입니다.


이제 다음의 글토막을 가늠하고, 펴고, 그리고 탐색하여 보십시오. 그리고 셋 중 어떤 것이 가장 적합한 방법인지를 생각해 봅시다.

Sound beep.   Sound


위의 글토막은 컴퓨터의 스피커에서 '삑!'하는 신호음을 발생시킵니다. 소리를 제대로 들으려면 컴퓨터의 소리 카드와 스피커가 제대로 설정되어있는지를 확인하십시오.


이번 글토막은 가늠하기, 펴기, 탐색하기 중에 어떤 것이 적당하겠습니까? 자, 우리가 지금 무엇을 하려고 하는지 생각해 봅시다. 우리는 신호음을 듣고싶었습니다. 다른 것은 필요치 않았습니다.


가늠하기의 경우에는 신호음만을 출력하고 다른 반응은 일어나지 않았습니다. 일단 현재까지는 가장 알맞은 방법인 것 같습니다.


펴기의 경우는 어떠했습니까? 신호음이 들리기는 했지만 "Sound"라는 결과값을 덩이에 씌워 일터에 폈습니다. Sound? 소리? 이 글토막이 펴졌을 때 분명히 소리가 났습니다. 그렇다고 화면에도 "Sound"라는 글자가 표시될 필요까지는 없었습니다.


탐색하기의 경우는 더욱 더 가관입니다. 앞서 탐색하기의 용도에서, "복잡한 값을 간단하게 여러 부분으로 쪼개어 관찰할 때 필요한 도구"라고 했습니다. 그런데 여기서는 어떻습니까? "Sound"라고 하는 결과값을 탐색해보니 오히려 무언지도 알 수 없는 복잡한 값들이 나타났습니다. Oh, my God! 물론 신호음이 들리기는 헀지만 필요 이상의 정보가 너무 많습니다.


결국 위의 글토막은 그냥 가늠하기만 사용하는 것이 제일 좋은 해결책이었습니다.


마지막으로 다음의 글토막을 가늠하고, 펴보고, 그리고 탐색해봅시다. 아래의 글토막은 현재 Smalltalk 환경에 존재하는 모든 갈래(class)들을 가나다순으로 정렬하여 가름모듬(sorted collection)에 담아내는 일을 합니다.

Class allClasses asSortedCollection


일단 위의 경우 단지 글토막을 가늠하기만 하면 아무런 변화도 일어나지 않게 됩니다. 그러므로 지금처럼 결과값을 알아보아야 할 상황에서는 가늠하기는 적절하지 못합니다. 그럼 이번에는 위의 글토막을 펴 봅시다. "1.3" 마디에서 나타난 것처럼, 일터에 결과들이 모두 다 출력된 것은 아닐 것입니다. 이것은 일터에 표시하기에는 내용이 너무 많기 때문에 발생한 결과로, 결과값의 끝에 "...etc..."라는 표시가 붙어있으며, 이는 결과들이 일터에 모두 다 표시되지 못했음을 알려줍니다. 따라서 글토막을 펴 보는 것만으로는 결과를 보기 어려웠습니다. 이제 위의 글토막을 탐색해봅시다. 그러면 "Inspecting a SortedCollection" 이라는 제목이 붙은 탐색기가 열릴 것입니다. 이 탐색기를 이용하면 앞서 글토막을 폈을 때보다 훨씬 편리하게 내용을 볼 수 있습니다.


이렇게 같은 글토막이라도 어떤 방식으로 결과값을 보느냐에 따라서 가늠하기, 펴기, 탐색하기의 유용성이 달라집니다.


앞으로 우리는 수많은 Smalltalk의 글토막을 가늠해 볼 것입니다. 그럴 때마다 여러분은 이 글토막을 어떤 방식으로 가늠할지를 고민하게 될 텐데, 앞으로 셋째 가름이 끝날 때쯤이면 이제는 생각하지 않고서도 무의식적으로 적당하게 글토막을 가늠하는 법을 익히게 될 것입니다.


그리고 보통때의 경우 '글토막을 가늠한다'는 말속에는 "글토막을 상황에 따라 적절하게 가늠하거나 펴거나 탐색한다"는 뜻이 포함되어있다는 것도 참고로 알아두시기 바랍니다.


다른 Smalltalk에서는...?


Dolphin Smalltalk가 아닌 다른 Smalltalk에서도 결과적으로 글토막을 가늠하는 방식에는 "가늠하기", "펴기", "탐색하기"의 세 가지 방식을 취합니다. 그러나 각각을 실행하는 방법이 조금 다를 수 있습니다. 이를테면, Smalltalk Express나 Smalltalk/V의 경우에, "가늠하기"를 'DO It', "펴기" 를 'Show It', "탐색하기"를 'Inspect It'이라고 부릅니다. 그리고 가늠할 때 쓰는 단축 글쇠 역시 Ctrl-D, Ctrl-S, Ctrl-I를 사용합니다.


그러나 기본적인 개념을 확실히 알고 있으면, Dolphin Smalltalk가 아닌 다른 환경을 접했을 때에도 당황하지 않게 될 것입니다. 중요한 것은 기초가 튼튼해야 한다는 말입니다.


셈! 셈! 셈!

컴퓨터가 처음 만들어진 목적은 "계산을 하기 위함"이었습니다. 한참 세계대전의 열기가 고조되고 있을 때, 어떻게 하면 목표물을 정확히 겨냥하여 대포알을 맞출 수 있을까라는 문제를 풀기 위하여 컴퓨터가 개발되었던 것이지요. 따라서 컴퓨터가 할 수 있는 일 중에 가장 기본외 되면서도 중요한 일은 셈을 하는 것, 즉 "계산"인 것입니다. 이제 우리는 Smalltalk 에서는 이러한 중요한 계산 기능이 어떻게 이루어지는지에 대해서 알아보도록 하겠습니다.


여러분은 이제 "1 + 2"라는 식을 계산하여 "3"이라는 결과값을 얻어내는 데는 자신이 있을 것입니다. 식당 개 삼 년이면 라면을 끓인다는 말이 있듯이 말입니다. ^^: 그런데 우리가 여태껏 가늠해 온 글토막의 모양을 조금 바꾸어서 가늠해 봅시다.


이를테면 " 1 + 2 . "과 같은 글토막을 Smalltalk는 어떻게 이해하고 가늠할까요? 백견이 불여일행이라, 한 번 위의 글토막을 펴볼까요?

1     +     2    .    3


어김없이 "3"이라는 결과값이 나왔습니다. 그럼 다음처럼 하면 어떨까요? 아래의 글토막은 세 줄로 되어있기 때문에, 글토막을 가늠하려면 세 줄에 덩이(block)를 씌워야 한다는 것, 잊어버리지 않았겠지요?

                                   1
       +
              2. 
 3


어떻습니까? 역시 "3"이라는 결과값이 나타났습니다.


결국 글토막에 빈칸이 들어가거나 줄이 바뀌는 경우라도, 제대로 덩이를 씌워서 가늠하기만 하면 아무런 문제없이 동작한다는 것을 알 수 있습니다.


그러나 숫자 중간에 빈칸을 집어넣거나, 숫자를 쓰다가 줄을 바꾸고 이어 쓰는 경우에 Smalltalk는 이 숫자들을 하나의 수로 보는 것이 아니라 __두 개의__ 수로 보기 때문에 문제가 생깁니다. 다음의 글토막을 펴 보십시오.

1234
5678 + 1.


[실행 결과]
"no period at end of statement 5678" 잘못 발생


위의 글토막을 가늠하면 "실행 결과"에 적은 것과 같은 잘못 알림글을 받게 됩니다. 그리고 잘못이 있는 "5678" 부분에 덩이가 씌워집니다. 위의 잘못 알림글을 직역해보면 "5678 문장의 끝에 마침표가 없습니다"라는 뜻이 되는데, 결국 여기서는 "12345678"이라는 수의 중간에 줄을 바꾸었기 때문에 일어나는 잘못입니다.


따라서 글토막에 빈칸을 넣거나 줄을 바꿀 때에는 수의 중간에 넣어서는 안되고, 수, 그리고 '+'와 같은 연산자(operator) 사이에서만 넣을 수 있다는 것을 알 수 있습니다.


지금까지 우리는 덧셈에 대해서 알아보았습니다. 이제부터 덧셈을 제외한 뺄셈, 곱셈, 나눗셈을 Smalltalk에서 어떻게 수행하는지 알아봅시다.


아래의 글토막을 펴 보고 결과를 확인해 보십시오.

4 - 1.   3    "뺄셈"
2 * 2.   4    "곱셈"
6 / 2.   3    "나눗셈"


"+"와 "-"는 수학에서 쓰는 기호를 그대로 사용합니다. 그러나 곱셈의 경우는 "*"을, 나눗셈의 경우는 "/" 기호를 사용합니다.


Smalltalk는 보통 우리들이 사용하는 계산기에는 없는 연산을 수행할 수 있습니다. 다음의 글토막을 각각 펴 봅시다.

25 // 3.   8          "나눗셈의 몫"
25 \\ 3.   1          "나눗셈의 나머지"


"//"는 나눗셈을 할 때의 몫을 구합니다. 반면 "\\"는 나눗셈에서 생긴 나머지를 구할 때 사용합니다.

25 = (3 * 8) + 1


위처럼 되기떄문에, 위의 글토막들이 왜 "8"과 "1"의 결과를 남겼는지 알 수 있겠지요?


지금까지 우리는 Smalltalk에서 간단한 사칙 연산(덧셈, 뺄셈, 곱셈, 나눗셈)을 해 보았습니다. 이제 조금 더 복잡한 식을 계산해 봅시다.


아래의 글토막을 각각 펴 보고 결과를 확인해 보십시오.

1 + 1 + 1.   3
2 * 2 * 2.   8
5 - 1 - 1 - 1.   2
12 / 3 / 2.   2


세 개의 수와 두 개의 연산자를 가지고 있는 식도 Smalltalk는 매우 훌륭하게 풀 수 있었습니다.


그러면 거꾸로, 이번에는 아주아주 간단한 글토막을 가늠해봅시다. 아주아주 간단하다? 그러면 숫자 하나로 이루어진 글토막을 생각할 수 있습니다.

1   1


"1"도 작지만 엄연히 하나의 글토막입니다. "1"을 펴 보면 무엇이 나옵니까? "1"을 펴 봤지 "1" 밖에 더 나오겠습니까? :)


양수(陽數)를 펴 보면 양수가 그대로 나왔습니다. 그럼 음수(陰數)라면?

-1   -1


말할 것도 없이 음수 "-1"을 펴면 똑같이 "-1"이 나타났습니다. (너무 멍청한 글토막만 가늠하고 있었나요?)


그럼 이번에는 조금 덜 멍청한 글토막을 가늠해보죠.

4 - 6.   -2
2 * -3.  -6


보는 바와 같이, 음수를 결과값으로 남기거나 음수 자신이 계산에 사용되더라도 Smalltalk는 매우 훌륭하게(!) 계산을 수행해 냅니다. 하긴, 200의 계승(factorial)도 거뜬히 구할 수 있는 Smalltalk가 한낱 음수 따위에 두려워하리요? :)


그럼 이제 슬슬 Smalltalk의 머리를 아프게 만들어 봅시다. 지금까지는 덧셈이면 덧셈, 곱셉이면 곱셈만 했습니다만, 지금부터는 이러한 연산자들을 마구, 마구 섞어서 계산해 봅시다. 도대체 Smalltalk는 어떤 반응을 보일까요?


자, 식을 계산하기 전에 우선 여러분이 한 번 생각해 보십시오. 아래 두 식을 Smalltalk로 풀면 똑같은 답이 나올까~요?

1 + 2 * 3
2 * 3 + 1


대답은 "땡"! '아니오'올시다. 그런지 안 그런지 직접 위의 식을 글토막으로 만들어서 가늠해 보시지요.

1 + 2 * 3   9
2 * 3 + 1   7


어라? "1 + 2 * 3"이 "9"야? 음, 이건 뭔가 잘못됐어! Smalltalk의 머리가 이제 슬슬 돌기 시작했나봐! 으악!


자, 당황하는 여러분의 얼굴이 보이지만, 일단 진정하고, 이야기를 계속하기 전에 몇 가지 낱말에 대한 의미를 좀 명확하게 해 둘 필요가 있을 것 같습니다. 들어보세요.


지금껏 제가 밥먹듯 써 온 "연산자"라는 말, 그게 무슨 뜻인가 하면, '+', '-', '*', '/', '\\', '//'처럼 어떤 연산, 즉 계산을 할 것인지를 나타내는 기호를 '연산자'(operator)라고 부릅니다. 그리고 이러한 계산에 사용되는 값을 '연산수'(operand), 또는 '피연산자'라고 부릅니다. 그러므로 "1 + 2" 에서 '+'를 "연산자"라 하고, '1'을 "+의 왼쪽 연산수", '2'를 "+ 연산자의 오른쪽 연산수"라고 부르게 되어있습니다.


"1 + 2 * 3" 의 경우를 생각해 봅시다. 이 식에서 연산자는 '+'와 '*'로 쉽게 알 수 있는데, 그럼 연산수는 무엇일까요? 이걸 금방 알아내기란 어렵습니다. 왜냐하면, '+'나 '*' 중에 어느 하나가 먼저 계산되고 난 다음에 비로소 연산수를 확실히 알 수 있기 때문입니다.


위에서 "1 + 2 * 3"을 Smalltalk에게 가늠하라고 했더니 엉뚱하게도 "9"라는 값이 튀어나왔습니다. Smalltalk가 틀린 걸까요? 물론 그렇게 생각할 수도 있지만, 다르게 생각하면 이것은 여러분의 착각일 수도 있습니다.


'+'와 '*'라는 두 개의 연산수 중에 어느 것이 먼저 계산이 되느냐에 따라 "1 + 2 * 3"의 결과값이 달라집니다.


  • 만일 '+'가 먼저 계산된다면...?
    "1 + 2"가 먼저 계산되고 따라서 위의 식은 "3 * 3"이 되어서 결과적으로 "9"라는 결과값이 나오는 것은 당연한 일! 따라서 이 경우 '+'의 연산수는 "1"과 "2", '*'의 연산수는 "1 + 2"(즉 "3")!
  • 만일 '*'이 먼저 계산된다면...?
    "2 * 3"이 먼저 계산되고 따라서 위의 식은 "1 + 6"이 되어서 결과적으로 "7"이라는 결과값이 나오는 것은 역시 당연한 일! 따라서 이 경우 '+'의 연산수는 "1"과 "2 * 3"(즉 "6"), '*'의 연산수는 "2"와 "3"!


이렇게 "1 + 2 * 3"이라는 식은 '+'를 먼저 계산하느냐 '*'을 먼저 계산하느냐에 따라 결과값이 달라지게 됩니다. 헷갈리죠?


여러분은 '*'이 '+'보다 당연히 먼저 계산되어야 한다고 주장하겠지만, 앞서 말했듯이 그건 여러분의 착각! 분명히 Smalltalk는 '*' 보다 '+'를 먼저 계산했다는 이야기! 그럼 Smalltalk가 틀리는가? 그렇지 않습니다. "로마에서는 로마법을!" 그러므로 Smalltalk에서도 Smalltalk의 법을 지켜야겠죠?


분명히 산수시간에 우리는 '+'보다 '*'를 먼저 계산하라고 배웠습니다. 그러나 Smalltalk는 '*'보다 '+'를 먼저 계산했습니다. 이러한 차이가 일어나는 까닭은, 바로 "연산의 우선 순위"(operator's precedence) 때문입니다.


산수에서는 여러 가지 연산자가 쓰이는데, 각각은 나름으로의 '우선 순위'를 가지고 있습니다. 이를테면 '+'과 '*'이 같이 있으면 '*'을 먼저 계산한다는 식으로 말입니다.


Smalltalk에서는 연산자의 우선 순위가 기본적으로 존재하지 않습니다. 일단 저렇게 계산식이 주어지면 Smalltalk는 무조건 __왼쪽에서 오른쪽으로__ 계산을 해 나갑니다. 즉, Smalltalk에서는 따로 연산자의 우선 순위를 정하지 않고, 왼쪽에서 시작해서 오른쪽 방향으로 연산수와 연산자들을 차례차례 계산해 나간다는 말입니다. 그래서....

1 + 2 * 3    3 * 3   9
2 * 3 + 1    6 + 1   7


위와같은 값이 나오게 되는 것입니다. 억지라구요? 말도 안 된다고요? 여러분의 머리뼛속깊이 파고든 "고정 관념"의 벽을 깨지 않으면, 여러분은 절대로 새로운 것을 배울 수 없습니다. Smalltalk가 그렇다는데 여러분이 어떻게 하겠습니까? Smalltalk의 처리 방식이 마음에 안 들어서 Smalltalk를 포기하겠습니까? 뭐, 결국 포기하신다면 어쩔 수 없겠지만,... Smalltalk는 적어도 계산식에 있어서 복잡한 것을 따지기 싫어합니다. 그냥 단순하게 앞에 나온 건 먼저 처리하고 그 다음에 나온 것을 처리한다, 그것입니다. 어렵습니까?

연산자의 우선 순위


연산자가 많기로 유명한 C와 C++ 언어는 모두 40개가 넘는 연산자를 가지고 있습니다. 이들은 분류에 따라 여러 갈래로 나눌 수 있는데, 각각은 저마다 연산의 우선 순위가 다르게 매겨져 있습니다. 또한 연산수를 취하는 방법도 왼쪽에서 오른쪽으로 취하는 방법도 있으며, 반대로 오른쪽에서 왼쪽으로 취하는 방법도 있습니다. 이를 "결합성"이라고 부르는데, 여하튼 연산자들을 종류별로, 연산의 우선 순위별로, 결합성 별로 분류하게 되면 종이 한 장을 가득 채우는 표가 하나 만들어집니다. 에고~ 머리야!


다른 언어의 경우도 별반 다를 것이 없습니다. Object Pascal 도 C/C++ 보다는 적지만 한 연산자 합니다! ^^: 역시 여기에서도 연산의 우선 순위가 있습니다. 겷합성도 당연히 존재하고요. C/C++과 파스칼을 같이 사용하는 사람이라면 두 언어에서의 연산의 우선 순위가 미묘하게 다르기 때문에 틀린 문법으로 바탕글(source code)를 쓰는 경우가 많지요(필자도 물론 속합니다만..)


베이식? 마찬가지입니다. 뭐 파스칼과 별반 다를 것이 없습니다.


이처럼 연산자를 가지고 있는 대부분의 언어들은 연산의 우선 순위를 만들어 놓고 이 규칙대로 바탕글을 쓰도록 하고 있습니다. 이러한 언어의 문법이 몸에 밴 사람이라면 몰라도, 처음 언어를 공부하는 사람들이라면, 특히 한 연산자 하는(!) C/C++ 언어를 공부하는 사람이라면, 연산자의 개수와 우선 순위 때문에 곤혹을 치러야 합니다. 여하튼 필자도 많이 고생한 기억이 나는군요.


물론 Smalltalk에서도 연산의 우선 순위 비슷한 것은 존재합니다. 바로 뒤에서 설명할 "길수의 우선 순위"(method's precedence)가 그것인데, 다른 언어에서처럼 그렇게 복잡하지 않습니다. 그러니 지레 겁먹지 마시길...


Smalltalk가 산수에서 쓰는 것과 다른 방식을 취해서 기분이 나쁘십니까? 글 쎄요. 과연 계산식이 다음처럼 복잡해도 그렇게 기분이 나쁠까요?

1 + 2 * 3 - 4 + 5 * 6 - 7 - 8 / 9   "?"


산수에서 쓰는 방법으로 위의 식을 한번 계산해 보십시오. ^^: 그러나 Smalltalk에서는 왼쪽에서 오른쪽으로 훑어가면서 계산하면 되므로 매우 간 단합니다. 아래는 Smalltalk가 위의 식을 어떻게 푸는지를 보여줍니다.

1 + 2 * 3 - 4 + 5 * 6 - 7 - 8 / 9  →
~~~~~
3     * 3 - 4 + 5 * 6 - 7 - 8 / 9  →
~~~~~~~~~
9         - 4 + 5 * 6 - 7 - 8 / 9  →
~~~~~~~~~~~~~
5             + 5 * 6 - 7 - 8 / 9  →
~~~~~~~~~~~~~~~~~
10                * 6 - 7 - 8 / 9  →
~~~~~~~~~~~~~~~~~~~~~
60                    - 7 - 8 / 9  →
~~~~~~~~~~~~~~~~~~~~~~~~~
53                        - 8 / 9  →
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
45                            / 9  →
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5


매우 쉽게 결과값이 나올 수 있다는 것을 느끼실 겁니다. Smalltalk에서도 물론 산수에서 쓰는 것처럼 똑같은 우선 순위의 규칙을 세울 수도 있었지만, 실제로 복잡한 계산식을 써야 하는 경우라면 오히려 우선 순위 규칙 때문에 복잡해질 수가 있습니다. 이는 결국 문법이 복잡해진다는 말이고, 그렇게 된다면 프로그래밍을 하는 사람의 노력이 더 들어간다고 봐야 옳습니다. 물론 이런 우선 순위 규칙을 처리하기 위해서 Smalltalk(정확히는 컴퓨터)가 해야 할 일도 더 많아질 것이고... 여러모로 Smalltalk는 현명한 선택을 한 것 같다는 게 필자의 생각입니다만....


물론 경우에 따라서 '+' 보다 '*'이 먼저 계산되어야 할 필요가 있습니다. 이 때에는 괄호를 사용하면 됩니다.

1 + (2 * 3)    1 + 6   7


이렇게 계산식 중에서 먼저 계산되어야 할 부분에 소괄호를 둘러주면, Smalltalk는 무조건 왼쪽에서 오른쪽으로 값을 계산하지 않고, 괄호가 있는 식을 먼저 계산하게 됩니다. 이것이 오히려 연산의 우선 순위 규칙을 정해주는 것보다 훨씬 명확한 바탕글을 쓸 수 있게 만들어줍니다.


괄호를 쓰면 연산의 우선 순위가 명확해 진다고 해서 아무 때나 괄호를 쓰는 것이 좋다는 말은 아닙니다.

(2 * 3) + 1    6 + 1   7


이 경우는 괄호가 있으나 없으나 똑같이 "7"이라는 결과값을 남깁니다. 아래에 쓸 데 없는 괄호의 예를 좀 더 들어보겠습니다.

(1 + 2 + 3).                     6
(1) + 2                          3
(((1 + 2) + 3) + 4) + 5          15
(((((1)))))                      1


위의 괄호들이 정말 쓸 데 없는지 알아보려면, 위의 글토막에서 괄호란 괄호는 다 뺀 상태로 다시 가늠해 보면 알 수 있습니다. 아마 똑같은 결과를 얻을 것입니다. 그러므로 여러분이 실제로 바탕글을 쓸 때에는 저런 짓(?)은 하지 않는 게 정신 건강상, 눈 건강상 좋습니다. (저 많은 괄호를 언제 다 세고 있다니???)


당연한 말이지만 괄호는 제 짝을 맞추어서 써야 합니다. 따라서 다음과 같은 글토막은 말도 안 되는, 그래서 Smalltalk마저도 거부하는 글토막입니다.

(1 + 2                  오른쪽 괄호는 어디에??
1 + 2)                  왼쪽 괄호는 어디에??
1) + (2                 어라? 왼쪽은 오른쪽이, 오른쪽은 왼쪽이?
(1+) 2                  "1+" 숫자인가? 연산수로는   없음.


위의 글토막을 정말 Smalltalk가 거부하는지는 아무거나 하나를 골라서 가늠해 보면 알 수 있습니다. 실제로

(1 + 2


를 가늠해 보면 "expecting ')'"이라는 잘못이 발생하게 됩니다. Smalltalk는 친절하게도 어디에서 괄호가 빠졌는지를 알려줍니다. ^^:


Smalltalk는 여러분이 어떤 실수를 할 때는 이처럼 잘못 알림글을 나타내거나, "1.5" 마디에서 잠깐 이야기했던 "발자취 창"(walkback window)를 통해 여러분의 잘못을 알려줍니다.


Smalltalk는 매우 안전한 시스템입니다. 여러분이 어지간한 실수를 하더라도 Smalltalk 자체가 죽어버리는 일은 극히 드믑니다. 물론 여러분이 Smalltalk 를 죽이게 할 수는 있습니다만, 고의가 아닌 이상 여러분의 실수로 인해서 Smalltalk가 박살나는 일은 없을 것입니다. 바꾸어 말하면, 실패를 두려워하지 말고 여러 가지 실험을 해 보라는 말입니다. "이렇게 하면 어떻게 될까?" 하는 탐구심은 Smalltalk처럼 여러분과 객체들이 언제나 자유롭게 대화할 수 있는 환경에서는 꼭 필요한 생각이기 때문입니다.


만약 Smalltalk 죽이기에 동참하고 싶으면 다음 글토막을 가늠해 보십시오. 이 때 여러분이 Smalltalk 본(image)에 저장할 것이 있다면 File > Save Image 메뉴를 사용하여 먼저 저장을 한 뒤에 가늠하도록 합시다. 괜히 저장하지도 못했는데 Smalltalk가 죽었다고 필자를 원망하지 말구요.

Processor := nil.


위의 글토막을 가늠하면 "Unrecoverable Stack Overflow Error..." 운운하면서 Smalltalk의 실행이 중지될 것입니다. 확인 단추를 눌러주면 Smalltalk 환경은 끝나고 운영체계로 되돌아가게 됩니다.


Smalltalk가 죽기는 했지만 다시 Smalltalk를 실행하면 이전에 여러분이 저장했던 본을 이용하여 모든 객체를 되살려 놓을 것입니다. 물론 Smalltalk가 죽는다고 운영체계까지 죽음의 수렁으로 몰아넣지는 않습니다.


Smalltalk 는 자신이 죽을 망정, 운영체계를 먹통으로 만들어버리는 일은 하지 않습니다. 그러므로 마음껏 실험하십시오. 그리고 실수하십시오. 그러면 여러분이 실수한 만큼 실력이 늘 것입니다. 물론 실수에 대비해서 언제나 깨끗한 Smalltalk 의 본(image)은 두 개 이상 준비해 두어야겠지요? ^^: ("1.4.2" 마디 참조)


실체와 갈래

우리는 지금까지 '1', '2', 3'처럼 소수점이 없는 수를 다루어 왔습니다. 이러한 수들을 수학에서는 정수(正數)라고 부릅니다. 즉 우리는 지금까지 정수 갈래(class)에 속하는 여러 가지의 수들을 다루어 온 것입니다. 방금 말했지만 정수는 수(數)의 한 "갈래"입니다. 그럼 정수에는 어떠한 것들이 있을까요? 산수 시간에 우리가 정수에 속하는 수를 어떻게 나타내었습니까?

....,-3, -2, -1, 0, 1, 2,....


처럼 나타내었습니다. 즉 '-3', '1', '2' 등은 모두 정수 갈래에 속한 수들이라는 뜻입니다.


앞서 필자는 "우리는 지금까지 정수를 다루었다"고 말했는데, 실은 정수를 다룬 것이 아니라 정수 갈래에 속해 있는 실제의 수를 다룬 것입니다. 다시 말하면 "정수"라고 하는 갈래를 직접 다루지는 않았습니다. 조금 복잡하죠?


그럼 다른 예를 들어봅시다. 여러분은 사과를 좋아합니까? 저는 사과를 참 좋아합니다. 그래서 "나는 사과를 먹는다"고 말할 수 있습니다. 그런데 엄밀하게 따져보십시오. 나는 "사과"라는 __이름__을 먹는 것이 아니라, "사과"라고 불려지는 실제로 존재하는 어떤 한 과일을 먹었을 따름입니다.


여러분은 개를 좋아합니까? 저는 개를 좋아합니다. 그런데 우리들이 "개를 좋아한다"고 말할 때, "개"라고 하는 갈래(class)를 좋아하는 것이 아니라, '개'라고 불려지는 실체(instance)를 좋아하는 것입니다. 잘 생각해 보십시오. "개"라고 하는 것은 사람들이 사물(object)과 사물을 구별하기 위해서 붙여놓은 갈래의 이름일 뿐입니다. (이야기가 갑자기 철학적이 되었죠? --:)


여러분은 저마다 이름을 가지고 있을 것입니다. 저는 "김찬홍"입니다. 그런데 저 찬홍이는 "사람"입니다. 즉 저는 사람이라는 __갈래(class)__에 속해있으며, 그러므로 찬홍이는 사람이라는 갈래의 __실체(instance)__입니다. 그래도 어렵습니까?


좀 더 머리에 와 닿는 이야기를 해 봅시다. 먹는 이야기입니다 :). 여러분은 붕어빵을 어떻게 만드는지 보셨습니까? 붕어빵을 만들 때에는, 우선 붕어빵을 만들기 위한 반죽이 필요합니다. 그리고 붕어빵의 속이 필요하겠지요. 이제 붕어빵을 만들 차례입니다. 우선 붕어빵 모양의 틀에 반죽을 넣고 준비한 속을 넣습니다. 그리고 뚜껑을 닫고 불에서 익히면 붕어빵이 되는 것이지요.(지금이 요리시간인가? 후후) 자, 여기서 붕어빵을 만들기 위한 틀이 갈래(class)입니다. 그리고 이 틀에서 만들어낸 붕어빵이 바로 실체(instance)가 되는 것입니다. 어렵습니까?


자, 다시 수 이야기를 해 봅시다. 우리들은 지금까지 정수를 가지고 여러 가지 실험을 해 보았는데, 사실 '정수' 자체를 가지고 실험을 한 것이 아니라, '1', '2', '3' 따위의 실체(instance)를 가지고 실험을 한 것입니다. 우리는 이러한 수를 정수라는 갈래(class)로 묶어서 부를 뿐입니다.


여기서 우리는 갈래(class)와 실체(實體, instance)에 대해서 이야기를 해야 합니다. 조금 어려운 개념일지는 모르겠지만, 조금만 곰곰이 생각해 보면 그렇게 어렵지만은 않습니다.


정수라고 하는 갈래에는 여러 개의 실체가 있습니다. 1, 2, 3, 4, -1, -2, -3, -4,... 이런 모든 것이 정수라는 __갈래__의 __실체__가 되는 것입니다.


거꾸로 생각해 봅시다. 우리는 실체들을 모아서 갈래를 짓습니다. 1, 2, 3, 4, -1, -2, -3, 4-,... 등은 모두 "소수점이 없다"는 공통점을 가지고 있습니다. 그래서 이런 공통점을 가지고 있는 실체들을 "정수"라는 갈래로 이름지어 부르는 것입니다.


이제 갈래와 실체의 관계를 어느 정도 파악할 수 있겠습니까? 잘 모르겠으면 더 나아가지 말고 처음부터 글을 되읽어봅시다. 그러면 분명히 와 닿는 무엇인가가 있을 것입니다. 원래부터 실체와 갈래는 조금 추상적인 개념이므로, 여러분이 헷갈리는 것이 오히려 당연할지도 모르겠습니다.


자, 갈래와 실체에 대해서 어느 정도 가닥을 잡았다면, 이제는 실습을 통해서 몸으로 느껴보도록 합시다. 앞에서 우리가 객체에 대한 이야기를 할 때, "모든 객체는 저마다 나름의 갈래에 속해있다"는 말을 한 적이 있을 것입니다. 기억나십니까? 우리들이 앞에서 다루어온 1, 2, 3, -1, -2, -3 등이 바로 객체(object)입니다. 결국 그 말은 1, 2, 3, -1, -2, -3 등이 모두 저마다의 갈래에 속해있다는 말입니다. 그럼 이들 1, 2, 3,...은 도대체 어떤 갈래에 속해있을까요? 말하나마나겠지요? "정수"라는 갈래에 속해 있다고 바로 조금 전에 이야기했습니다.


그래서 우리들은 이러한 수에 대해서 "도대체 너는 어떤 갈래에 속하느냐"라고 물을 수 있습니다. 그럼 이제 '1'에 대해서 "너는 어떤 갈래에 속하느냐"고 물어봅시다. 다음의 글토막을 가늠하여 펴 봅시다.

1 class   SmallInteger


어떻습니까? '1'이 뭐라고 대답했습니까? "SmallInteger"라고 대답했습니다. "Integer", 즉 바로 "정수"라고 대답하지는 않았지만 "작은 정수"라는 썩 비슷한 대답을 했습니다. 그러므로 수 '1'은 "작은 정수"(SmallInteger)의 실체(instance)라고 말할 수 있습니다.


여기서 멈출 수는 없습니다. 1 이 SmallInteger 의 실체라면, 그럼 Smalltalk 에 Integer라는 갈래는 없는 것일까요? 분명히 "작은 정수"보다 "정수"가 좀 더 일반적입니다. 그러므로 분명히 "작은 정수"라는 갈래 위에 좀 더 일반적인 갈래, 즉 "정수"와 같은 갈래가 있을지도 모릅니다.


이제 그럼 우리는 "정수"에게 "좀 더 일반적인 갈래가 무엇인가"라는 물음을 던질 필요가 있습니다. 이제 우리는 실체에게 묻는 것이 아니라 갈래에게 묻는 것입니다. 다음 글토막을 펴 봅시다.

SmallInteger superclass   Integer


"class"가 "너는 어떤 갈래에 속하느냐"고 묻는 것이었다면, "superclass"는 "네 바로 위에 있는 윗갈래는 무엇이냐"고 묻는 것입니다. 윗갈래? 도대체 그게 뭘까요?


쉽게 말하면 윗갈래(superclass)란 지금 현재의 갈래 바로 위에 존재하는 갈래를 말합니다. "사과", "과일", "식물"이 있을 때, "사과"는 "과일"에 속하고, "과일"이라는 갈래는 다시 "식물"이라는 갈래에 딸려있습니다.

              식물
               ↑
              과일
           ↗
      사과(실체)


위의 그림과 같이 갈래가 씨줄(hierarchy)로 엮어져 있을 때, "윗갈래"와 "아랫갈래"의 관계가 이루어집니다.


위에서, '과일'은 '사과'가 속한 "갈래"(class)이고, '사과'는 '과일'의 "실체"(instance)입니다.


이 상태에서 '식물'은 '과일'의 "윗갈래"(superclass)이고, '과일'은 '식물'의 "아랫갈래"(subclass)입니다.


그러면 위의 보기를 바탕으로, 실체 '1'과 'SmallInteger', 'Integer' 갈래들의 관계를 아래에 그려봅시다.

              Integer
                 ↑
            SmallInteger
          ↗
      1(실체)


'SmallInteger'는 '1'이 속한 "갈래"이고, '1'은 'SmallInteger'의 "실체"입니다.


이 상태에서 'Integer'는 'SmallInteger'의 "윗갈래"(superclass)이고, 반대로 'SmallInteger'는 'Integer'의 "아랫갈래"(subclass)입니다.


어렵습니까? 위의 그림을 잘 보고 씨줄이 어느쪽으로 흘러가는지를 파악하면 "아랫갈래"와 "윗갈래"를 금방 파악할 수 있을 것입니다.


자, 지금까지 우리는 '1'은 'SmallInteger'의 "실체"라는 것을 알았으며, 'SmallInteger'는 'Integer'의 아랫갈래라는 것을 알 수 있었습니다. 그러면 'Integer'의 윗갈래는 무엇일까요? 그리고 그 위의 윗갈래는...? 우리는 윗갈래가 무엇인지 묻는 방법을 이미 공부했습니다. 배운 것을 그대로 써먹어 봅시다.

Integer superclass               Number
Number superclass                ArithmeticValue
ArithmeticValue superclass       Magnitude
Magnitude superclass             Object
Object superclass                nil


보시다시피 우리는 정수(Integer)에서 출발하여 결국은 "객체"(object)에 이르기까지 씨줄을 더듬어 올라갔습니다. Object에서 멈춘 이유는 "Object"의 윗갈래는 없기 때문입니다. 어떻게 알 수 있느냐구요? "Object" 갈래가 그렇게 대답했습니다.

Object superclass        nil


"nil"은 "없다"는 뜻입니다. 즉 "Object" 갈래에서 씨줄이 멈추는 것입니다. 앞으로 우리는 "nil"을 "헛"이라 부릅시다.


그럼 지금까지 알아본 것을 바탕으로 씨줄그림(hierarchy diagram)을 그려보면 아래와 같이 됩니다.

그림 2-1::SmallInteger 갈래의 씨줄그림

              Object(객체)
                 ↑
              Magnitude(크기값)
                 ↑
          ArithmeticValue(산술값)
                 ↑
              Number(수)
                 ↑
              Integer(정수
                 ↑
            SmallInteger(작은정수)
          ↗
      1(실체)


위의 그림을 보고 우리는 다음과 같이 말할 수 있습니다.


"1은 작은 정수이고, 정수이고, 수이며, 산술값이고, 크기값이며 객체이다."


즉 우리가 "사과는 과일이며 동시에 식물이다"라고 말할 수 있는 것과 똑같다고 할 수 있습니다.


그래서 '1'은 정수인 동시에 '수'라는 윗갈래에도 속해있음을 알 수 있습니다. 또한 '1'은 바로 "객체"라는 것도 확실히 알 수 있습니다. Smalltalk에서는 모든 것이 객체입니다.


그러면 여기서 의문이 하나 생깁니다. "작은 정수"(SmallInteger)가 있다면 그 어디인가 "큰 정수"(LargetInteger)라는 갈래도 있어야 옳습니다. '1'이 작다? 그럼 '1'보다 더 큰 수를 입력해 보면 어떨까요? 자, 계속 실험을 해 봅시다.

2 class                  SmallInteger
3 class                  SmallInteger
10 class                 SmallInteger
100 class                SmallInteger
100000 class             SmallInteger
100000000 class          SmallInteger
1000000000 class         SmallInteger
9999999999 class         LargeInteger
10000000000  class       LargeInteger


찾았습니다! 역시 SmallInteger의 반대가 되는 LargeInteger라는 갈래가 분명히 있습니다. 그러므로 정수(Integer)에 대해서 다음과 같은 씨줄그림을 그려볼 수 있습니다.

                   정수(Integer)
                   ↗      ↖
   SmallInteger(작은정수)  LargeInteger(큰정수)


이제 우리는 여기까지 오면서 두 가지의 사실을 배울 수 있었습니다.

  1. class - 이 실체가 어떤 갈래에 속해있는가?
  2. superclass - 이 갈래의 윗갈래는 무엇인가?


여기서 한 가지 재미있는 실험을 해 봅시다. 만일 실체가 아니라 갈래에 대해서 superclass를 쓰면 어떻게 될까요? 직접 한 번 실험을 해 봅시다.

0 superclass   "발자취 창 열림"


위의 식을 펴 보면 "SmallInteger does not understand #superclass" 라는 제목을 가진 발자취 창(walkback window)이 나타남을 알 수 있습니다. 위의 말을 우리말로 하면 "작은 정수는 superclass를 알아들을 수 없습니다" 정도로 고쳐볼 수 있습니다. 이는 Smalltalk가 우리들이 글토막을 가늠할 때 어떤 잘못이 있었음을 알려줍니다. 보통의 경우 이렇게 튀어나온 발자취 창은 그냥 닫아도 상관이 없습니다. 그러나 창을 닫기 전에 도대체 이 창의 제목이 무엇인지 정도는 읽어보도록 합시다. 창의 제목은 왜 이 창이 열리게 되었는지를 설명해 주기 때문입니다.


앞에서 우리는 또 한가지의 사실을 알았습니다. 바로


superclass는 갈래에만 사용할 수 있고, 실체에는 사용하지 못한다.


는 것입니다. '0'이 실체인지 갈래인지 아직까지 구분이 안 가는 분들은 수고스럽겠지만 처음부터 글을 다시 읽어보십시오. 실체와 갈래가 자꾸 헷갈리는 상태에서 계속 글을 진행하면 오히려 더 큰 혼란의 수렁으로 빠져들기 때문입니다.


그럼 이제 갈래에 "class"를 써먹을 수 있는지 알아봅시다. 다음의 글토막들을 펴 보고 결과를 확인해봅시다.

1 class                  SmallInteger
SmallInteger class       SmallInteger class
Number class             Number class 
Object class             Object class


일단 갈래에 대해서 "class"를 써먹을 수 있었습니다. 그러나 그 결과값은 도무지 쉽게 설명하기가 어렵습니다. 갈래더러 "너는 무슨 갈래에 속하느냐?"라고 물은 꼴이 되는데, 이 때 Smalltalk는 갈래 이름 뒤에 "class"를 붙여서 나타냈습니다. 왜 이런 결과값이 나왔는지 설명하려면 "풀이갈래"(metaclass)에 대해서 이야기를 꺼내야 하는데, 지금 막 Smalltalk를 공부하는 여러분에게 자칫하면 커다란 혼란을 드릴 수 있기 때문에 여기서는 설명하지 않겠습니다. 그리고 지금 당장은 여러분이 풀이갈래에 대해 모른다고 해서 Smalltalk를 공부하는데 문제가 생기지는 않을 것이므로, 다음과 같은 사실만 짚고 넘어갑시다.


class는 실체와 갈래 모두에 사용할 수 있다.


이정도만 알면 충분합니다.


지금까지 우리는 "class"를 사용하여 어떤 실체가 무슨 갈래에 속하는지를 알아보았습니다. 그런데 종종 우리들은 윗갈래를 모두 포함한 씨줄을 뒤져서 어떤 실체가 무슨 씨줄에 얽혀있는지 알아야할 필요가 있습니다. 다시 말하면, 찬홍이는 "학생"이라는 갈래에 속해있지만, 동시에 '사람'이기 도 합니다. 그래서 "찬홍이는 사람인가?"라고 물을 수 있듯이, Smalltalk의 실체에게도 "1은 정수인가?"라는 식으로 씨줄을 바로 물을 수 있다는 말입니다. 아래의 글토막을 펴 보십시오.

2 isKindOf: SmallInteger         true
2 isKindOf: Integer              true


'2'는 "true"라는 값을 남겼습니다. "true", 즉 "맞다"는 말입니다.


"isKindOf:"를 사용하면 그림 2-1 에 그린 씨줄을 모두 훑어가면서 확인할 수 있습니다.

2 isKindOf: Number               true
2 isKindOf: ArithmeticValue      true
2 isKindOf: Magnitude            true
2 isKindOf: Object               true


그럼 "isKindOf:"를 사용하여 틀린 질문을 해 봅시다.

2 isKindOf: LargeInteger                 false
10000000000 isKindOf: SmallInteger       false


역시 실체들은 우리가 틀린 것을 물으면 "false", 즉 "틀렸다"라고 대답합니다. 이 세상에 과연 이렇게 옳은 것을 옳다, 틀린 것을 틀렸다고 말할 수 있는 사람이 과연 몇 명이나 될까요? -.-:


그럼 마지막으로 Smalltalk에게 짓궂은 장난을 해 봅시다. Smalltalk가 알아들을 수 없는 갈래 이름으로 "isKindOf:"를 사용했을 때 어떤 반응을 보일까요? 다음의 글토막을 펴 봅시다.

2 isKindOf: Abcd         "Error: undeclared Abcd 잘못 발생"


Smalltalk는 "Abcd"라는 갈래 이름을 못알아듣습니다. 그래서 잘못을 일으킨 것이지요. 결국 Smalltalk에는 "Abcd"라는 갈래는 없다는 말이 되는데, 그러면 Smalltalk에 무슨 갈래가 있는지 어떤 방법으로 알 수 있을까요? 그것은 여러분에게 숙제로 남겨두겠습니다. 힌트는, 지금까지 읽은 글 중에 분명히 답이 있습니다. 제 기억으로는 아마 두 번 정도 답이 나온 것 같은데요.... 한 번 찾아보십시오. :)


지금까지 우리는 실체(instance)와 갈래(class)에 대해서 알아보았습니다. Smalltalk에서 모든 것은 객체(object)이기 때문에 "2.1" 마디에서 이야기했던 "객체의 특성"이 그대로 적용됩니다. 즉 "모든 객체는 저마나 나름으로의 갈래에 속한다"는 것입니다. 우리는 이번 나눔[部]에서 어떤 객체가 있을 때 이 객체가 도대체 어떤 갈래에 속하는지, 그리고 그 갈래의 윗갈래는 무엇인지를 아는 방법을 공부해 보았습니다. 어떻습니까? Smalltalk는 언제든지 여러분이 필요하다면 지금까지 한 것처럼 객체와 매우 쉽게 대화를 할 수 있습니다.


다음 나눔에서 우리는 '지시'(message)에 대해서 공부하게 될 것입니다. 모든 객체가 행동을 하려면 어떤 지시를 내려야 합니다. 사실 우리는 벌써 지금까지 객체에게 무수한 지시를 내렸습니다. "class", "superclass"는 물론이고 "isKindOf:"도 하나의 지시였습니다. 물론 '+', '-'와 같은 연산자(operator) 역시 어엿한 지시입니다. 이제 다음 나눔에서 우리가 할 일은 이런 지시에는 어떤 종류가 있으며, Smalltalk의 객체는 어떤 지시를 어떻게 받아서 결과를 남기는지에 대해서 알아보도록 하겠습니다.


다른 언어에서는...?

Smalltalk는 순수한 객체지향형 언어입니다. C++, Object Pascal 등의 언어가 이미 있던 언어에 객체지향의 성격을 더하여 확장한 것에 비해, Smalltalk는 모든 것을 객체지향적 관점으로 새롭게 시작한 언어입니다. 따라서 객체지향이라는 관점에서 보면 분석과 설계, 그리고 구현이 가장 잘 된 언어가 또한 Smalltalk일 것입니다. 그러므로 Smalltalk에서는 모든 것이 객체라는 사실은 전혀 놀랄만한 일이 아닙니다. '1', '2'와 같은 수도 그래서 객체입니다. 이 객체에게 우리는 알아들을 수 있는 지시(message)를 내릴 수 있으며, 객체는 우리가 내린 지시에 대해서 알고있는 길수(method)대로 행동합니다. 이것이 모든 객체지향언어의 특징인 동시에 Smalltalk의 특징입니다.필자는 Simula 67에 대해서 잘 모르기 때문에, 최초로 갈래(class)라는 개념을 사용한 Simula 언어에 이렇게 객체지향적인 특성이 있는지는 모르겠습니다. 허나, Smalltalk는 객체지향언어의 선두주자로써 한 몫을 차지합니다. 이 후 생겨난 객체지향 언어나, 또는 기존 언어에 객체지향 기법을 더한 언어들은 크든 작든 간에 Smalltalk의 특징을 수용했습니다. 이번 글에서 이야기한 것들은 "Object Reflection"이나 "RTTI: Runtime Type Information" 이라는 이름으로 이미 Java나 다른 많은 언어들이 반영하고 있는 부분입니다.


C++나 Object Pascal에서는 모든 것이 객체가 아닙니다. 어떤 것은 객체이고 어떤 것은 아닙니다. '1', '2'와 같은 것은 객체가 아니기 때문에, 언어에서 제공하는 객체지향적인 특성을 전혀 사용할 수 없게 됩니다. C++나 Object Pascal에서 '1'에게 어떤 갈래에 속해 있느냐고 물을 수 없는 이유가 여기에 있습니다.


C++와 Object Pascal의 문법이 복잡해지는 근본적인 이유 또한 기존 언어에 객체지향 패러다임을 접목시키려는 노력에서 비롯되었습니다. 이들은 새 술을 헌 부대에 담으려 했습니다. 물론 그것이 꼭 잘못된 것만은 아니지만, 어쨌건 필요 이상으로 복잡한 문법을 만든 것만은 사실입니다. 앞으로 여러분 이 Smalltalk를 공부하면서 느끼겠지만, Smalltalk에서는 그 문법이 매우 간결하며 간단합니다. 이것은 '객체지향'이라고 하는 통일되고 잘 정리된 개념을 쓰기 때문에 가능한 것입니다.


물론 Smalltalk가 이렇게 순수 객체지향 패러다임을 추구하기 때문에 생겨난 단점들도 있다고 생각되며, 실제로도 많은 문제점이 지적되어왔습니다. 바로 이것은 객체지향이 나아가야 할 방향을 제시하고 있다고 필자는 생각합니다. 그렇지만 결국 Smalltalk는 객체지향의 모토이자 바탕이라고 아니할 수 없습니다. 적어도 대화식이라는 장점으로 인하여 객체지향이라는 개념에 한 발 한 발 접근하기는 번역(compiling) 언어보다도 대화식 언어가 쉽다고 필자는 생각합니다. 물론 이것은 필자의 생각입니다만...


지시에 대하여...

Smalltalk에서는 모든 것이 객체(object)입니다. '1', '2' 등과 같은 숫자는 물론이고 심지어는 여러분이 늘 글토막을 짓고, 고치고, 가늠하고 하는 일터(workspace)며, 글토막을 가늠할 때 생기는 잘못을 알려주는 발자취 창(walkback window) 등 Smalltalk를 이루는 모든 구성요소들은 객체입니다. 이러한 객체들은 지시(message)를 주고받으며 일을 합니다. 앞서 우리는 주로 수(數)에 대해서 여러 가지 지시를 내려보았습니다만, Smalltalk에서는 수 말고도 여러 가지 다양한 객체가 준비되어있어 여러분의 지시를 기다리고 있습니다.


먼저 앞서 공부한 것을 복습한다는 생각으로 다음의 글토막을 펴 봅시다.

1 class   SmallInteger
1 + 2   3
1 isKindOf: Integer   true


위에서 "class", "+ 2", "isKindOf: Integer"는 모두 객체 '1'에게 보내는 지시입니다. 보다시피 같은 객체에게 내리는 지시라도 상황에 따라 서로 다른 모양을 하고 있습니다.


Smalltalk에는 크게 다음 세 가지 모양의 지시가 있습니다.

  • 외마디 지시(unary message)
  • 겹마디 지시(binary message)
  • 쇠마디 지시(keyworded message)


"class"처럼 간단한 낱말로 이루어진 지시를 '외마디 지시'라 부르고, "+ 2"처럼 왼쪽 연산수와 오른쪽 연산수에 의해 겹으로 싸여있는 지시를 '겹마디 지시'라 부릅니다. 그리고 "isKindOf: Integer"처럼 쌍점을 달고 다니는 지시를 '쇠마디 지시'라고 부릅니다.


"isKindOf: Integer" 지시의 경우만 살펴보면 겹마디 지시와 쇠마디 지시가 썩 달라 보이지 않지만, 쇠마디 지시는 필요에 따라 좀 더 복잡해질 수 있습 니다.

5 between: 1 and: 10   true


위의 글토막은 '5'에게 "1과 10 사이에 있는 숫자냐"고 물어보는 쇠마디 지시의 또 다른 형태입니다. '5'는 당연히 '1'과 '10' 사이에 들어 있는 숫자 이므로 "true"(맞다)라는 결과값을 남겼습니다. 똑똑한 5! ^^;


'쇠마디 지시'에서의 '쇠'는 "열쇠말"을 줄인 말입니다. "열쇠말"(kwyword)을 사전에서 찾아보면 "문장을 푸는데 열쇠(단서)가 되는 말"(한글과컴퓨터, 1998, 프라임 영한사전)이라고 되어있습니다. 그래서 위의지시에서 "isKindOf:", "between:", "and:" 등은 모두 바로 뒤에 있는 수가 어떤 의미 인지를 알 수 있는 열쇠말이 되는 것입니다. 그리고 Smalltalk에서의 열쇠말은 반드시 쌍점(:)으로 끝맺어야 합니다. 요컨데, "쇠마디 지시"는 "열쇠말로 이루어진 지시"를 말합니다.


따라서 아래와 같은 글토막은 틀린 쇠마디 지시입니다.

5 between 1 10          "열쇠말이 없음"
5 between 1 and 10      "열쇠말은 반드시 쌍점으로 끝나야 함"
5 between: 1 and 10     "'and'도 열쇠말이므로 쌍점이 필요함"


그러므로 쇠마디 지시를 사용할 때에는 각각의 열쇠말들이 쌍점으로 끝나는지를 반드시 확인해야 합니다. 그리고 쇠마디 지시는 필요하다면 얼마든지 많은 열쇠말을 가지고 만들 수 있습니다. 물론 다시 말하지만, 열쇠말은 반드시 쌍점으로 끝나야 한다는 것, 잊지 마세요!


앞서 우리는 "1 + 2"라는 글토막에 대해서 다루면서 '연산자'(operator)와 '연산수'(operand)에 대해서 이야기한 적이 있습니다. '연산자'는 어떤 연산을 나타내는 기호를 말하고, '연산수'는 연산에 필요한 값이라고 했습니다. 그렇다면

1 isKindOf: Integer             "(1)"
5 between: 1 and: 10            "(2)"


의 경우를 연산자와 연산수에 빗대어 생각해 봅시다. (1)의 경우 연산수는 '1', 'Integer'가 될 것입니다. 마찬가지로 (2)의 경우 연산수는 '5', '1', '10'이라고 생각할 수 있습니다. 그런데 (2)의 경우 연산자는 무엇일까요? 이 때의 연산자는 "between:and:"입니다. 즉 쇠마디 지시에서의 연산자는 바로 열쇠말이라는 것을 알 수 있습니다.


Smalltalk에서는 수식이 아닌 곳에 '연산자'와 '연산수'라는 말을 쓰지 않습니다. 대신 "보낸이"(sender)와 "받는이"(receiver), "인자"(parameter)라는 말을 사용합니다. 일반적으로

5 class
5 + 2
5 between: 1 and: 10


이라는 글토막이 주어졌을 때, '5'라는 한 개의 정수를 "받는이"(receiver) 라 하고, "class", "+ 2", "between: 1 and: 10"을 각각 지시(message)라 부릅니다. 또한 지시는 길표(selector)와 인자(因子, parameter)로 이루어져 있는데, 이 중에서 "class", "+", "between:and:" 를 "길표"라 하고, 길표에 딸려서 받는이의 연산자 역할을 하는 '2', '1', '10'을 "인자"라 부릅니다. "class" 라는 길표는 외마디 지시이기 때문에 아무런 인자도 갖지 않습니다.


말로 설명하니까 많이 복잡해졌는데, 위에 설명한 것을 쉽게 표현하면 다음 그림과 같습니다.

그림 2-2::받는이, 보낸이, 길수, 길표, 인자의 관계

받는이  지시
_____   _____
5       class
        ~~~~~
        길표

받는이       지시
_____   _____________
5       +       2
        ~~~~~   ~~~~~
        길표    인자

받는이                     지시
______  __________________________________________
5       between:        1       and:    10
        ~~~~~~~~        ~~~~~   ~~~~    ~~~~~
           |            인자      |     인자
           +-----------[길표]-----+


그림으로 설명하니까 더 모르겠다구요? (!) 음, 여하튼 여러분은 머리가 좋으시기 때문에 이 정도로 설명하면 충분히 알아들으시겠지요? :)


자, 다시 한 번 정리해 봅시다.

  • 받는이: 실제로 지시를 받는 객체를 말합니다. Smalltalk에서는 받는이가 제일 앞머리에 나오게 되어있습니다.
  • 지시: 받는이에게 보내야 할 내용을 말합니다. 글토막이 있을 경우 보통은 받는이를 뺀 나머지 부분이 지시가 됩니다.
  • 길표: 지시의 구성요소 중 하나로, 객체에게 어떤 길수(method)를 골라서 행동할 것인지를 알려주기 위해 사용됩니다. 길표 역시 지시의 종류에 따라 외마디 지시에 사용되는 "외마디 길표"(unary selector), 겹마디 지시에 사용되는 "겹마디 길표"(binary selector), 쇠마디 지시에 사용되는 "쇠마디 길표"(keyword selector)로 나누어 부를 수도 있습니다.
  • 인자: 길표와 함께 넘겨주는 추가적인 정보입니다. 받는이는 길표를 보고 무엇을 해야 할지를 결정하고, 주어진 인자를 바탕으로하여 어떤 일을 합니다.


이처럼 Smalltalk의 글토막은 여러 부분으로 이루어져 있으며, 각 부분은 나름대로의 이름과 역할을 가지고 있다는 것도 알 수 있습니다. 이제 앞으로 우리는 많은 Smalltalk의 글토막들을 보게 될 터인데, 이들은 한결같이 받는이와 길표, 인자로 이루어진 지시로 되어 있습니다. 지금까지 가늠해본 여러 가지 글토막들을 살펴보면서 받는이, 지시, 길표, 인자로 나누어 봅시다. 그래서 글토막을 이루고 있는 부분들과 그 이름들에 익숙해지도록 노력합시다.

다른 언어에서는...?


C++, Object Pascal, Java에서도 앞서 이야기한 받는이, 지시, 길표, 인자라는 말이 그대로 적용됩니다. 물론 문법은 좀 다르지만 말입니다.

우선 C++에서 살펴봅시다.

cout << "Hello, World!";


위의 경우에 'count'이 받는이가 되고, '<< "Hello, World!"'는 지시가 됩니다. 또한 '<<'는 길수이며(ostream::operator<<()), "Hello, World"는 '<<'에 딸린 인자입니다. 위에서 ';'는 문장을 끝내는 마침표 역할을 합니다.

aPoint.create(1, 2);


위에서 'aPoint'는 받는이이며 나머지는 지시, 그리고 'create'를 길표, '1' 과 '2'는 인자가 됩니다.


C++에서 포인터가 어떤 객체를 가리키고 있을 때에는...

pCar->go(FORWARD);


의 경우는 pCar가 가리키는 객체가 받는이이며, 나머지 부분이 지시가 됩니다. '.'이 아니라 '->'를 쓴 것은, 위에서 'pCar'가 포인터 변수이기 때문에, 포인터가 가리키는 객체를 끄집어내기 위해서 쓰인 것입니다.


파스칼과 자바의 경우도 대동소이한데,

Application.CreateForm(MainForm, TMainForm);
System.println("Hello, World");


위에서 'Application', 'System'은 모두 받는이미여, 나머지 부분은 지시가 됩니다. 괄호 속에 있는 MainForm, TMainForm, "Hello, World!"는 모두 인자이며, CreateForm과 println은 모두 길표입니다.


이처럼 각 언어마다 형성되는 문법은 다르지만 객체지향의 개념 적용은 같다는 것을 알 수 있습니다. 이것이 C++, Java, Object Pascal이 '객체지향 언어'라고 불릴 수 있는 중요한 요소가 됩니다. 즉 언어의 문법은 다르지만 모두 '객체지향'이라는 개념이 적용되는 것이지요.


쇠마디 지시와 바탕글의 기독성


다른 언어에는 Smalltalk에서와 같이 "외마디 지시", "겹마디 지시", "쇠마디 지시"라는 개념이 없습니다. 대부분 객체에 딸린 함수(function)나 절차(procedure)를 부른다는 개념을 가지고 있습니다. 그래서 객체에게 넘겨주어야 할 인자가 많을 때에는 Smalltalk의 쇠마디 지시가 훨씬 바탕글을 읽기 좋게 만듭니다.

aSubString := originalString.Copy( 10, 5 );


보다는

aSubString := original copyFrom: 10 to: 15


쪽이 바탕글을 쉽게 읽을 수 있습니다. 또한

between(5, 1, 10);


보다는

5 between: 1 and: 10


이 경우가 훨씬 읽기가 쉽습니다. 너무 길어 보인다고 생각하시는 분이 계시다면 저는 이런 말씀을 드리고 싶습니다. 바탕글(source code)은 한 번 쓰여지고 여러번 읽혀집니다. 그러므로 바탕글이 길어져도 읽기가 편하면 유지하고 관리하는데 매우 편합니다. 헝가리안 표기법을 왜 사용하는지를 생각해 보세요.


지시의 우선 순위

바로 앞 나눔에서 우리는 Smalltalk 의 지시를 세 가지의 서로 다른 종류로 나눌 수 있다는 것을 공부했습니다. 무엇 무엇이 있었습니까?

5 class                 외마디 지시(unary message)
5 + 2                   겹마디 지시(binary message)
5 between: 1 and: 10    쇠마디 지시(keyword message)


또한 외마디 지시를 나타내기 위해서 사용하는 길표를 "외마디 길표"(unary selector), 겹마디 지시를 나타내려면 "겹마디 길표"(binary selector)를, 쇠마디 지시를 나타내기 위해서 사용하는 길표는 "쇠마디 길표"(keyword selector)라고 부른다는 것도 공부했습니다.


우리가 Smalltalk로 문제를 풀기 위해서는 위에서 설명한 여러 종류의 지시를 섞어서 써야 합니다. 이 말은 글토막 하나가 한 개의 지시로 이루어진 것이 아니라 두 개 이상의 지시를 함께 포함하고 있을 수 있다는 말입니다. 가장 간단한 예를 들면

1 + 2 * 3


이 있습니다. 위의 경우 '+'와 '*'라는 두 개의 길표(selector)가 있기 때문에, 결국 위의 글토막은 두 개의 지시를 포함하고 있다는 것을 알 수 있습니다.


앞에서 우리는 '+', '-', '*', '/'와 같은 겹마디 지시를 두 개 이상 사용했을 때

1 + 2 * 3


위의 글토막을 가늠하면 산수에서는 '7'이 되지만 Smalltalk에서는 '9'가 된다고 했습니다. 즉 겹마디 지시가 두 개 이상 있을 때에는 왼쪽에서부터 오른쪽으로 처리된다는 규칙을 공부했습니다.


그렇다면 종류가 서로 다른 외마디 지시와 겹마디 지시, 그리고 쇠마디 지시를 한꺼번에 사용했을 때에는 어떤 식으로 처리될까요? 이번 나눔에서 알아볼 중요한 문제는 바로 이러한 여러 가지 지시들의 우선 순위(precedence)입니다.


이야기를 진행하기 전에, 우선 정수 갈래(Integer class)에 속한 객체에게 보낼 수 있는 외마디 길표 두 개를 알아보겠습니다.


다음 글토막을 펴 보십시오.

1 negated        -1
-1 negated       1
0 negated        0
123 negated      -123


길표 "negated"는 이 지시를 받는이(receiver)의 부호(符號)를 바꾸어줍니다. 즉 양수에게 "negated"를 보내면 음수를 남기고, 음수에 "negated"를 보내면 양수를 남깁니다. '0'의 경우에는 부호가 없기 때문에 "negated" 지시에 대해서 아무런 변화 없이 그대로 '0'을 남깁니다.


다음은 우리가 이미 "1.3"마디에서 가늠해 본 길표 "factorial"입니다.

0 factorial      1
1 factorial      1
2 factorial      2
3 factorial      6
4 factorial      24
5 factorial      120
6 factorial      720


길표 "factorial"은 받는이에 대한 계승(階乘, x!)을 구합니다. 예를 들어 "6 factorial"은 "5 * 4 * 3 * 2 * 1"과 같은 결과값을 남깁니다. 그래서 "200 factorial"의 경우는 결과값이 기하급수적으로(물론 수학적으로는 틀린 말이겠지만) 늘어나게 되는 것입니다. 시험삼아 "2000 factorial"이라는 글토막을 펴 봅시다. 과연 Smalltalk가 "200"도 아닌 "2000"의 계승을 구할 수 있을까요? :)


자, 앞서 우리는 겹마디 길표를 두 개 사용하여 다음과 같은 글토막을 만들어 편 일이 있습니다.

1 + 2 * 3   9


그렇다면 겹마디 길표에서 했던 것처럼, 외마디 길표로 나온 결과에 다시 외마디 길표를 적용할 수 있을까요? 다음의 글토막을 펴 봅시다.

1 negated negated   1


결과값으로 "1"이 남겨졌습니다. 보다시피 위의 글토막은 외마디 지시를 두 개 이상 사용하는 것이 가능하다는 것을 보여줍니다. 그럼 어째서 위의 글토막이 "1"이라는 결과값을 낼 수 있었을까요? 가장 쉽게 알 수 있는 방법은, Smalltalk가 하는 방식대로 우리가 스스로 글토막을 가늠해 보는 것입니다.

1 negated negated →
~~~~~~~~~
-1        negated →
~~~~~~~~~~~~~~~~~
1


먼저 "1 negated"가 가늠되고 결과값으로 '-1'이 남게 됩니다. 그러므로 결국은 "-1 negated"가 되어서 최종 결과값은 "1"이 되는 것입니다. 위에서 볼 수 있듯이 외마디 지시를 연달아 사용하면 겹마디 지시의 경우와 마찬가지로 __왼쪽에서부터 오른쪽으로__ 처리되는 것을 볼 수 있습니다.


그렇다면 이번에는 factorial과 negated 길표를 섞어서 아래처럼 글토막을 만들어 봅시다.

3 factorial negated   -6


결과값으로 "-6"이 나왔는데, 위의 글토막 역시 우리가 스스로 가늠해보도록 합시다.

3 factorial negated →
~~~~~~~~~~~
6           negated →
~~~~~~~~~~~~~~~~~~~
-6


위의 경우는 먼저 왼쪽의 "3 factorial"이 가늠됩니다. 그 결과 "6"이라는 결과값이 나오고, "6 negated"가 되어서 "-6"이라는 결과값을 내게 됩니다.


즉 외마디 길표나 겹마디 길표 모두 왼쪽에서부터 오른쪽으로 차례로 길표를 처리합니다. 왼쪽에 있는 길표를 처리한 다음 그 결과를 다시 그 다음에 있는 길표를 이용하여 처리하는 것입니다.


지금까지는 왼쪽에 있는 길표부터 차례로 처리되었습니다. 그럼 이번에는 종류가 다른 두 개의 길표를 섞어서 사용해 봅시다. 우선 외마디 길표와 겹마디 길표를 아래처럼 섞어서 써 보면 어떤 결과값을 얻게될까요?

1 negated + 2   1


위의 경우는 "1 negated"가 가늠되어 '-1'을 남기고 결과적으로 "-1 + 2"가 되어서 "1"이라는 결과값이 남게 되었습니다. 여기까지 실행된 것으로 보아서는 왼쪽 길표가 처리되고 난 다음에 오른쪽 길표가 처리되는 규칙이 똑같이 적용되는 듯 합니다. 그러나....

1 + 2 negated   -1


어라? 길표 "negated"의 위치를 바꾸면 우리가 생각하는 결과가 나오지 않았습니다. 만약 왼쪽에 있는 길표부터 차례로 처리한다면 위의 글토막은 "-3"이라는 결과값을 내야 마땅합니다. 그러나 답은 "-1"입니다. 그럼 왜 이런 일이 일어날까요? 이제부터 머리가 조금 복잡해지기 시작하는데....


Smalltalk가 위의 글토막을 만나면 아래처럼 가늠합니다.

1 + 2 negated →
    ~~~~~~~~~
1 + -2 →
~~~~~~
-1


모든 길표(selector)에는 길표의 종류에 따라 우선권(priority)이 매겨져 있습니다. 이 경우에는 겹마디 지시보다 외마디 지시가 더 높은 우선권을 갖고 있습니다. 그래서

1 + 2 negated


의 경우는 겹마디 길표 "+"와 외마디 길표 "negated", 이렇게 두 종류의 길표가 섞여 있기 때문에 외마디 길표인 "negated"가 먼저 처리되는 것입니다.


그럼 다음의 글토막에서 제일 먼저 가늠되는 부분은 어디겠습니까?

1 + 2 + 3 factorial


겹마디 길표보다 우선권이 높은 "factorial"이 먼저 가늠될까요, 아니면 가장 왼쪽에 있는 "+" 길표가 먼저 가늠될까요? Smalltalk는 아래와 같이 가늠합니다.

1 + 2 + 3 factorial →
~~~~~
3     + 3 factorial →
        ~~~~~~~~~~~
3     + 6 →
~~~~~~~~~
9


위의 경우는 또 맨 왼쪽에 있는 '+'가 먼저 처리되었습니다. "factorial"은 왼쪽의 '+' 길표가 처리되고 난 뒤에 처리되었습니다. 그렇다면 단순히 우선권이 높다고 해서 먼저 처리되는 것만은 아니라는 이야기인데....


Smalltalk에서 여러 종류의 길표들이 있을 때에는 다음과 같은 규칙으로 처리됩니다.

우선권 규칙


  • 글토막의 맨 왼쪽부터 길표를 하나씩 꺼내서 바로 인접한 오른쪽의 길표와 우선권을 비교한다.
  • 두 길표의 우선권이 같거나 왼쪽 길표의 우선권이 높으면, 왼쪽 길표를 먼저 처리한다.
  • 오른쪽 길표의 우선권이 왼쪽 길표보다 높다면, 이제는 오른쪽 길표와 그 바로 옆에 있는 길표의 우선권을 비교하여 위와 같이 처리한다.
  • 이 작업을 글토막이 끝날 때까지 되풀이한다.


위와 같은 규칙을 "우선권 규칙"이라고 부르는데, 이는 Smalltalk가 글토막을 가늠해야 할 상황이라면 언제든지 어디에서건 적용되는 규칙이므로 잘 기억해야 할 필요가 있습니다.


"1 + 2 + 3 factorial"에서 왜 "factorial"이 아니라 "+"가 먼저 처리되었는지 이해가 되십니까? 바로 글토막의 왼쪽에서부터 길표를 비교하여 그 중에서 우선권이 높은 것을 먼저 처리해 나가기 때문입니다. 위의 경우 처음 두 개의 길표는 "+"와 "+"이고 우선권이 같으므로 왼쪽에 있는 "+"가 먼저 처리된 것입니다. 그래서 "1 + 2"가 먼저 계산된 것이지요. 그 결과 "3 + 3 factorial"이 되었을 것이고, 이번에는 앞서와는 반대로 "+"와 "factorial" 중 외마디 길표인 "factorial"이 먼저 가늠되어 "3 + 6"이 됩니다. 결국 결과값 "9"가 남게되겠지요.


이제 각각의 길표가 어떤 우선 순위를 갖는지 아래에 정리해 두겠습니다.

우선권 길표
1 외마디 길표(unary selector)
2 겹마디 길표(binary selector)
3 쇠마디 길표(keyword selector)


위의 표에서 정리한대로, "외마디 길표"가 가장 높은 우선권을 가지고 있으며, 다음이 "겹마디 길표", 마지막으로 "쇠마디 길표"가 가장 낮은 우선권을 갖게 됩니다.


Smalltalk를 공부하면서 반드시 알고 넘어가야 할 중요한 사항이 몇 가지 있는데, 앞서 설명했던 갈래(class)와 실체(instance)의 관계를 알아야하고, 지금 설명하고 있는 "우선권 규칙" 역시 매우 중요한 것이기 때문에 소흘이 넘어갈 수 없습니다. 우선권 규칙을 제대로 알고 있어야만 여러분이 객체에게 마음대로 지시를 내릴 수 있고, 또 다른 사람이 써 놓은 바탕글(source code)도 틀리지 않고 읽을 수 있기 때문입니다.


이제 조금 복잡한 보기를 들어봅시다. 아래 보기는 외마디 길표, 겹마디 길표, 그리고 쇠마디 길표 등 모든 종류의 길표가 다 등장합니다.

25 between: 10 + 3 factorial and: 5 factorial + 3


여러분이 "우선권 규칙"을 제대로 이해하고 있다면 위의 글토막이 어떤 순서로 가늠되는지를 알아맞힐 수 있어야 합니다. 이제 머릿속으로 위의 글토막을 차근차근 가늠해 봅시다. 그리고 아래 나와 있는 정답과 어느 정도 비슷한지를 살펴보도록 합시다.

25 between: 10 + 3 factorial and: 5 factorial + 3 →
                 ~~~~~~~~~~~
25 between: 10 + 6           and: 5 factorial + 3 →
            ~~~~~~  
25 between: 16               and: 5 factorial + 3 →
                                  ~~~~~~~~~~~
25 between: 16               and: 120         + 3 →   
                                  ~~~~~~~~~~~~~~~
25 between: 16               and: 123 →
   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
true


복잡합니까? 여러분이 생각한 것과 비슷했습니까? 자, 위의 순서를 따라가면서 좀 더 꼼꼼하게 따져봅시다.


첫 번째 줄을 봅시다.

25 between: 10 + 3 factorial and: 5 factorial + 3


위에서 첫 번째로 나오는 길표는 무엇입니까? 쇠마디 길표인 "between:and:" 입니다. 쇠마디 길표의 경우는 여러 개의 열쇠말이 모여서 하나의 길표를 이루는데, 글토막을 가늠할 때에는 하나의 열쇠말을 길표 하나처럼 생각해도 무방합니다. 그러므로 여기서는 "between:" 글쇠말이 처음으로 나오는군요. 그럼 두 번째 나오는 길표는 무엇입니까? 겹마디 길표인 "+" 입니다. 처음 길표는 쇠마디 길표이고, 두 번째는 겹마디 길표입니다. 어느 쪽이 우선권이 높습니까? 겹마디 길표가 우선권이 높습니다. 그러므로 "between: and:" 보다 "+" 쪽이 우선권이 높기 때문에 이 경우 "+" 길표가 먼저 처리되어야 합니다.


이제 "+"를 처리하기 위해서 또다시 바로 옆에 있는 길표와 우선권을 비교해야 합니다. "+" 다음에 있는 길표는 무엇입니까? 외마디 길표 "factorial"입니다. 그럼 "+"보다 우선권이 높은 "factorial"이 먼저 처리되어야 합니다.


"factorial" 오른쪽에는 무엇이 있습니까? 쇠마디 길표 "between:and:"의 두 번째 열쇠말인 "and:"가 있습니다. 이 열쇠말 역시 쇠마디 길표의 한 부분이므로, "factorial"의 우선권이 높습니다. 결과적으로 "factorial"이 맨 처음 처리되어야 합니다.


그렇다면 "factorial"의 지시를 받는이는 누구입니까? 네, 정수 "3"입니다. 그래서 정수 객체 "3"에 "factorial"지시가 내려지고, 객체는 결과값으로 "6"을 냅니다.


모진 고생 끝에 "factorial"이 처리되어 얻어진 결과는 다음과 같습니다.

25 between: 10 + 6           and: 5 factorial + 3


이제 위의 글토막을 다시 왼쪽에서부터 가늠합니다.


처음 길표는 "between:and:"의 열쇠말인 "between:"이고, 두 번째는 "+" 입니다. 우선권이 높은 "+"가 먼저 처리되어야 합니다. "+"를 처리하기 위해서 바로 옆에 있는 길표를 찾습니다. 열쇠말 "and:"로군요. 그렇다면 겹마디 길표인 "+"의 우선권이 높으므로 "+"를 처리합니다.


"+" 길표의 지시를 받는 객체는 무엇입니까? 이 경우 "10"이 됩니다. 인자는 방금 "factorial"에 의해서 처리된 "6"입니다. 그래서 정수 객체 "10"에게 "+ 6"이라는 지시가 내려지고 다음과 같은 결과를 얻게 됩니다.

25 between: 16               and: 5 factorial + 3


자, 위의 결과를 보면 두 개의 열쇠말 중 "between:"열쇠말에 걸린 인자는 모두 처리되어 하나의 객체 "16"이 되었음을 알 수 있습니다. 그렇기 때문에 이제는 "and:" 열쇠말에 걸려있는 인자를 처리해야 합니다.


처음 등장하는 길표는 "between:and:" 의 두 번째 열쇠말인 "and:" 입니다. 바로 오른쪽에 있는 길표는 "factorial" 이군요. 이 경우 "factorial"이 높은 우선권을 갖기 때문에 먼저 처리됩니다. 보다나마 "factorial"은 외마디 길표이므로 오른쪽 길표를 볼 것도 없이 그냥 처리해 버리면 됩니다. 여기서 "factorial" 길표로 지시를 받는 객체는 정수 "5"입니다. 그래서 아래와 같은 결과가 나옵니다.

25 between: 16               and: 120         + 3


어느새 글토막이 매우 간단해 졌습니다. 앞서 한 것처럼 앞의 "between:"에 걸린 객체는 하나 뿐이므로, 뒤의 "and:" 열쇠말에 걸려있는 나머지 인자를 풀어봅시다. 이제부터는 아주 쉽습니다.


이 경우 남아있는 길표는 열쇠말 "and:"와 겹마디 길표 "+"이므로 당연히 "+"가 먼저 처리됩니다. 아울러 "+"는 이 글토막에서의 마지막 길표이므로 "+"의 다음 길표를 찾을 필요가 없겠지요. "+" 지시를 받는이는 "120"이며 인자는 "3"입니다. 정수 객체 "120"에 "+ 3"이 지시되었고, 그 결과가 다음처럼 나왔습니다.

25 between: 16               and: 123


이제 남아있는 길표는 "between:and:" 뿐이므로 이 길표를 처리하면 글토막을 모두 가늠한 셈이 됩니다. 정수 객체 "25"에게 "between: 10 and: 123"이라는 지시가 내려지고, 객체 "25"는 "16"과 "123" 사이의 값을 가지고 있기 때문에 당연히 "true"를 결과값으로 냅니다. 이로써 글토막 하나를 다 풀었습니다.


확실히, 처음 Smalltalk를 배우는 사람들에게는 복잡한 글토막을 머릿속으로 풀어보는 일이 쉽지만은 않습니다. 그러나 머릿속으로 글토막을 풀어보는 것은 "우선권 규칙"을 익히는데 매우 큰 도움이 됩니다. 나아가 이렇게 익힌 우선권 규칙은 여러분이 어떤 객체에게 복잡한 지시를 내려야할 때 위력을 발휘합니다.


어떤 글토막이든지 __한 번에 하나씩__ 길표가 처리되어 지시가 내려집니다. 그러므로 앞에서 우리가 헀던 것처럼 왼쪽에서부터 길표들의 우선권을 차근차근 하나씩 비교하고 어떤 길표가 처리될지를 생각한다면 어렵지 않게 글토막을 풀어낼 수 있을 것입니다.


여러분이 글토막 풀이를 좀 더 연습할 수 있도록 아래에 몇 개의 글토막을 준비해 보았습니다. 여러분 스스로 글토막을 풀어서 결과를 가늠해 보십시오. 그런 다음 Smalltalk에 글토막을 쳐 넣고 펴서, 여러분이 풀어낸 결과값과 같은지를 확인해 봅시다. 만약 여러분이 생각한 결과와 일터에서 편 결과가 다르다면 어디서 실수를 했는지를 찾아보기 바랍니다.

1 + 2 + 3 factorial
10 factorial factorial + 1
1 + 2 factorial negated
1 between: 0 + 0 and: 2 factorial
1 negated between: 0 + 1 and: 5
2 factorial + 3 factorial


아래에 위의 글토막에서 제일 먼저 처리되는 지시와 그 지시를 받는 객체를 밑줄로써 표시해 놓았습니다. 여러분이 글토막을 가늠하는데 도움이 되기를 바랍니다. 만약 여러분이 생각한 것과 다르다면, "우선권 규칙"을 제대로 이해하지 못한 것이기 때문에, 번거롭더라도 글을 처음부터 다시 한 번 읽어보도록 합시다.

 + 2 + 3 factorial
~~~~~   
10 factorial factorial + 1
~~~~~~~~~~~~
1 + 2 factorial negated 
      ~~~~~~~~~
1 between: 0 + 0 and: 2 factorial
           ~~~~~   
1 negated between: 0 + 1 and: 5
~~~~~~~~~
2 factorial + 3 factorial
~~~~~~~~~~~


자, 마지막으로 알아볼 것은 "괄호"에 대한 것입니다. 우리는 "2.2" 마디에서 괄호에 대한 이야기를 하면서 다음의 글토막을 가늠해 본 적이 있습니다.

1 + (2 * 3)  1 + 6   7


즉 괄호를 둘러준 식이 먼저 계산된 다음 나머지 식이 계산됩니다. 그렇다면 "우선권 규칙"은 괄호에 대해서 어떤 영향을 받게 될까요?


답은 아주 간단합니다. 괄호가 있는 글토막의 경우도 마찬가지로 똑같이 우선권 규칙이 적용됩니다. 그러나 다음의 규칙 하나가 더 첨가됩니다.


우선권에 의해서 처리되는 길표의 인자가 괄호를 둘러쓰고 있으면 그 괄호 안에 있는 식을 모두 풀어서 하나의 객체로 만든 뒤에 해당 길표가 처리된다.


말로 하니까 매우 어려워 보이지만, 다음의 보기를 보면 어려운 규칙이 아니라는 것을 금방 알게됩니다.


"1 + (2 * 3)"의 경우를 생각해 봅시다. 일단 괄호 밖에는 "+" 길표밖에 없습니다. 그러므로 이 길표를 처리해야할텐데, "+" 길표의 인자가 괄호를 둘러쓰고 있습니다. 그러므로 "+"를 처리하여 "1"에게 지시를 보내려면 괄호를 풀어야 합니다. 따라서 괄호 안에 있는 "*" 길표가 먼저 처리된 것입니다.


좀 더 복잡한 예를 생각해 봅시다.

1 + 2 * (3 - 4 + (5 * 6)) + 3


위의 경우 괄호가 있기는 하지만 똑같이 우선권 규칙을 적용합니다. 그럼 "+"와 "*" 길표를 보면 되는데, 두 길표의 우선권이 같으므로 "+"가 먼저 처리됩니다.

3 * (3 - 4 + (5 * 6)) + 3


이제 다시 글토막을 가늠해 보면, 괄호 밖에 있는 길표들만 생각해 볼 때, 처음 나오는 길표는 "*"이고 다음으로 "+"가 나옵니다. 역시 두 길표의 우선권이 같으므로 "*"가 먼저 처리되어야 합니다. 그런데...


"*" 길표는 "3"에게 지시를 보내는데, 인자로 "(3 - 4 + (5 * 6))"을 가지고 있습니다. 그런데 이 인자가 괄호를 둘러쓰고 있으므로 "*"가 처리되려면 먼저 괄호를 풀어야 합니다.


자, 잠시 다른 것은 생각하지 말고 괄호 안에 있는

3 - 4 + (5 * 6)


만 생각합시다. 이 경우 괄호 안에 있는 길표는 생각하지 말고 밖의 길표만 살펴보면 "-", "+" 이렇게 두 개가 있습니다. 우선권 법칙에 의해서 앞에 나온 "-"가 먼저 처리되면

-1 + (5 * 6)


의 결과가 남게 됩니다. 이제 "+"를 처리해야하는데 역시 괄호가 걸립니다. 그래서 괄호를 풀어버리면

-1 + 30  29


가 남게 됩니다. 이제 괄호를 다 풀고 남은 "29"를 원래 식에 그대로 붙이면

3 * 29 + 3


이 됩니다. 이 상태에서 두 개의 길표는 모두 우선권이 같으므로 "*"가 먼저 처리될 것이고 따라서

87 + 3  90


을 결과값으로 얻게 됩니다.


정리하면, 괄호가 들어있는 글토막이라도 보통 다른 글토막처럼 우선권 규칙을 적용합니다. 이 때 괄호 안에 들어있는 길표들은 무시해 버립니다. 그러면 훨씬 쉽게 생각할 수 있습니다. 즉 괄호를 하나의 덩어리(=수) 정도로 생각하라는 말입니다. 그리고 우선권 규칙에 의해서 그 괄호가 길표의 인자가 될 때 해당 괄호를 풀어버리면 쉽게 해결할 수 있습니다. Smalltalk 보다 훨씬 더 복잡한 "연산자의 우선 순위 규칙"도 수학을 배울 때 잘 써먹던 여러분이기 때문에 Smalltalk의 "지시 우선권 규칙"은 아무 것도 아니라는 생각이 들 것입니다. 맞습니까? :)


이번 나눔에서는 여러 종류의 길표가 섞여있는 지시가 어떻게 처리되는지에 대해서 공부했습니다. 앞으로 여러분이 Smalltalk 프로그래밍을 쓰게 될 때 매우 중요한 내용이기 때문에 조금 지루하실 거라는 생각을 하면서도 글이 길어지게 되었습니다. 여하튼 "갈래와 실체"의 관계를 파악하는 것처럼 "우선권 규칙"을 제대로 파악하는 것도 매우 중요하다는 것을 기억해 두시기 바랍니다.

다른 언어에서는...?


설명이 길어지다 보니 "우선권 규칙"에 대해 매우 어렵게 느끼시는 분들이 있을 것 같은데, 실제로 "우선권 규칙"은 매우 간단합니다. 여러분은 "우선권 규칙"보다 몇 배나 더 복잡한 C/C++, Java, Object Pascal과 같은 복잡한 언어의 문법도 소화해 내지 않았습니까? 물론 지금 이 글을 읽는 분께서 프로그래밍에 경험이 없으시거나 아주 적다면, 여러분은 "우선권 규칙"보다 더 어려운 문법들이 다른 언어에 존재한다는 것을 위안(?)으로 삼아서 조금 힘들더라도 "우선권 규칙"을 공부해 두는 것이 좋습니다. 할 수만 있다면 C++ 언어에서 제공하는 40여개의 연산자를 우선순위별로 정리해 놓은 표를 첨부해드리고 싶지만, 제 힘이 너무 달리는군요. 누가 올려주실 분 없어요????


어쨌건 "우선권 규칙"은 Smalltalk의 문법을 아주 간단하게 만들면서 동시에 매우 자유로운 표현을 할 수 있도록 만들어주는 거라고 저는 생각합니다. 여러분은 동의하지 않으십니까? :)


고급 셈! 셈! 셈!

Smalltalk에는 "정수"(integer) 말고도 "분수"와 "소수"라고 하는 두 개의 수 갈래가 있습니다. 이번 나눔에서는 이들 새로운 두 개의 갈래가 어떤 것이며, 이 갈래에 속한 객체들은 어떤 지시를 알아들을 수 있는지에 대해서 공부합니다. 아마 이번 나눔을 끝내게 되면 여러분은 Smalltalk 프로그래밍에서 수를 처리하는 대부분의 사항에 대해서 공부했다고 할 수 있습니다.


우리는 "2.2.2" 나눔에서 정수 갈래에 속하는 여러 개의 실체(instance)에 대해서 다양한 지시를 내려보았습니다. 기본적으로 정수 객체들이 이해할 수 있는 지시는 어떤 것이 있었습니까? 아래에 지금까지 우리가 정수에게 여러 가지 지시를 내리기 위해 사용했던 길표들을 정리해 보았습니다.

길표 설명
x + y 두 수를 더한다
x - y x에서 y를 뺀다.
x * y 두 수를 곱한다.
x / y x를 y로 나눈다.
x // y x를 y로 나눈 몫을 얻는다.
x \\ y x를 y로 나눈 나머지를 구한다.
x negated x의 부호를 바꾼다.
x factorial x의 계승(x!)을 구한다.


"2.2.2"에서 우리는 이들 대부분의 길표에 대해서 다루어보았습니다. 그리고 "우선권 규칙"을 공부할 때에도 이러한 여러 가지의 지시를 내렸었습니다. 그러나 유독 우리는 나눗셈에 대해서는 많은 예를 들지 않았습니다. 그것은 나눗셈이 덧셈, 뺄셈, 곱셈 등의 다른 계산과는 달리 좀 유별난 구석을 가지고 있는, 한 마디로 말하면, 천덕꾸러기이기 때문입니다.


자, 그럼 이제부터는 이 천덕꾸러기 나눗셈에 대해서 집중적으로 알아보도록 합시다. 우선 다음 글토막을 펴 봅시다.

2 / 2            1
8 / 4            2
10 / 5           2
120 / 5          24


보다시피 나눗셈을 하기 위해서는 객체에게 "/" 길표를 사용하여 지시를 내리면 됩니다. 그런데 위에서 가늠했던 네 개의 수식은 모두 나누어 떨어지는 것들이었습니다. 그러나 나눗셈은 반드시 딱 나누어 떨어지라는 법은 없습니다. 다음 글토막을 펴 봅시다. 이 글토막은 나누어떨어지지 않는 식입니다.

1 / 2   1/2


어라? "1 / 2"를 가늠했더니 결과값으로 "1/2"가 나왔습니다. 이게 무엇일까요? 일단 정수 "1"은 "/ 2"라는 지시를 받아서 "1/2"라는 객체를 내놓기는 했습니다. 그럼 도대체 "1 / 2"라는 글토막을 가늠해서 나온 "1/2"라는 녀석은 도대체 어떤 갈래에 속할까요? 다음과 같이 해 봅시다.

(1 / 2) class   Fraction


위에서 왜 괄호를 사용했는지 아시겠습니까? 바로 앞 나눔에서 설명했듯이 외마디 길표인 "class"가 먼저 실행되는 것을 막기 위함입니다. (잘 모르겠으면 앞 나눔부터 글을 다시 읽으시는 수 밖에....)


"Fraction"을 사전에서 찾아보면 바로 "분수"(分數)라고 되어있습니다. 즉 "1 / 2"처럼 딱 나누어떨어지지 않는 경우, 정수 객체는 결과값으로 분수를 남긴 것입니다. 결과적으로 "1 / 2"를 가늠해서 나온 "1/2"는 "이 분의 일" 이라고 읽는 분수인 것입니다. 물론 수학에서 분수를 쓸 때에는

1
-
2


처럼 쓰겠지만, 한 줄에 분수를 나타내고 싶을 때에는 분자와 분모 사이에 빗금표(/)를 넣어서 씁니다. 그러므로 "1/2"는 틀림없이 분자와 분모를 가지고 있는 "분수"입니다.


그렇다면 여기서 한 가지 의문이 생깁니다. 언뜻 보면 분수 "1/2"와 식 "1 / 2"는 매우 비슷해 보입니다. 물론 "1 / 2"를 가늠하면 "1/2"가 되기 때문에 둘이 같건 다르건 무슨 상관이냐고 생각하는 사람이 있을는지 모르겠습니다. 그러나 "1/2"가 정말 완전한 분수냐 아니냐는 매우 중요합니다. 만일 "1/2" 이 분수라면 이는 "1/2" 도 어엿한 한 개의 객체가 될 것입니다. 그러나 만약 "1/2"가 분수가 아니라 "1 / 2"를 다르게 쓴 것에 불과하다면? 즉, 이것은 "1"이라는 객체에게 "/2"라는 지시를 보내는 글토막에 지나지 않는다는 말입니다.


어렵게 생각할 것 없이 일단 아래 글토막을 펴서 실험을 해 봅시다.

1/2 class        "발자취 창 열림"


만일 "1/2"가 하나의 객체라면 이 객체에게 "class"라는 지시를 내릴 수 있어야 옳습니다. 그러나 결과는 그렇지 않았습니다. 엉뚱하게도 발자취 창이 열리면서 "SmallIntege는 #divideIntoInteger:를 알아듣지 못합니다"라는 알림글을 받게 됩니다. 도대체 위의 글토막에 SmallInteger 라고는 없어 보이는데.... 더구나 우리는 "divideIntoInteger:"라는 지시를 내린 적이 없는데 어찌 된 일일까요?


자, 덩이를 씌워 가늠했던 글토막을 자세히 살펴봅시다. 분명히 우리가 처음 일터(workspace)에 입력한 글토막과 비교해 보면 무언가 다른 것이 보일 것입니다. 무엇일까요? 네, 바로 "색깔"입니다. 우리는 글토막을 쳐 넣을 때 색깔에 대해서는 전혀 신경 쓰지 않았습니다. 그러나 Smalltalk가 글토막을 가늠하자마자 각각의 말마디에 색을 입히기 시작했습니다. 위의 경우 "1", "2"는 보라색이, "/"와 "class"는 파란색이 입혀졌습니다. 그럼 잠시 분수 얘기는 젖혀두고, 다음 몇 개의 글토막을 가늠해 봅시다. 이번에는 결과값보다는 가늠된 글토막에 Smalltalk가 어떻게 색깔을 입히는지에 주의를 기울이고 보기 바랍니다.

5 class                 "5" 보라색, "class" 파란색.
5 + 2                   "5", "2" 보라색, "+" 파란색.
5 between: 1 and: 10    "5", "1", "10" 보라색, "between:", "and:" 파란색.


잘 보면 Smalltalk가 가늠한 글토막에 색을 입히는데는 어떤 규칙이 있어 보입니다. 무슨 규칙이 숨어있을까요? 아마 한참 전부터 눈치를 채신 분들도 계실지 모르겠습니다. 바로 글토막을 이루는 여러 가지 성분들을 각기 다른 색으로 표시하는 것입니다. 글토막은 보통 객체와 길표로 구성되어 있습니다. 바로 Smalltalk에서 객체는 보라색으로, 길표는 파란색으로 표시한 것입니다. 이렇게 글토막을 구성하는 여러 가지 성분들에 각기 다른 색을 입혀서 바탕글을 읽기 쉽게 만들어주는 기능을 "문법 돋이"(syntax highlight)라고 부르는데, 바로 우리가 사용하고 있는 Dolphin Smalltalk가 이 문법돋이를 지원해 주는 것입니다. 그러므로 글토막을 보면 어떤 것이 객체이고 어떤 것이 길표인지를 좀 더 쉽게 확인할 수 있습니다.


요즘 출시되고 있는 대부분의 개발 환경들이 문법 돋이를 지원합니다. 그러나 처음부터 모든 개발환경이 문법돋이를 제공하지는 않았습니다. 그것은 우리가 지금 사용하고 있는 Smalltalk도 마찬가지인데, 최근에 들어서 Smalltalk에도 문법돋이의 바람이 불고 있는 듯 합니다. 제가 써 본 Smalltalk 시스템은 Smalltalk Express, Smalltalk/V, Smalltalk MT, Dolphin Smalltalk 정도입니다. 그 중에서 정확하고 확실하게 문법돋이를 제공하는 환경은 Dolphin 뿐이었습니다. 제가 Smalltalk를 배우기 위해서 여러 가지 환경을 탐색했지만, 그래도 Dolphin을 선택한 이유는 이처럼 만들어 놓은 바탕글을 읽기가 매우 쉬웠다는 것도 하나의 큰 이유가 됩니다.


다시 분수 얘기로 되돌아옵시다. 앞서 우리는 다음과 같은 글토막을 가늠해 보았습니다.

1/2 class


물론 위의 경우 발자취 창이 나타나기는 했지만, 분명히 Smalltalk는 글토막을 가늠하기 위해서 나름대로 해석을 하기는 했습니다. 위의 경우 "class"는 누가 머래도 객체에게 보내지는 길표이기 때문에 파란색으로 표시되었습니다. 그러나 '1/2'의 "/"을 자세히 보면 역시 파란색으로 표시됨을 알 수 있습니다. 이 말은 무슨 말일까요? 바로 '1/2'의 '/'는 "길표"로 해석했다는 것을 보여줍니다.


결국 우리는 "1 / 2"나 "1/2" 모두 똑같은 계산식임을 알 수 있습니다. 단지 길표 앞뒤에 빈칸이 있는지 없는지의 차이일 뿐입니다. 실은 "/" 뿐만 아니라 모든 겹마디 지시는 붙여서 써도 아무런 상관이 없습니다.

1+2   3
5-3   2
6//4   1
6\\4   2


단지 위와 같이 쓰게 되면 바탕글을 읽기 어렵게 되기 때문에 Smalltalk 에서는 관례적으로 겹마디 길표의 앞뒤에는 한 칸의 빈칸을 넣을 뿐입니다. 그러므로 "1 / 2"와 "1/2"는 당연히 똑같은 식임을 알 수 있습니다.


그러나 결국 "1 / 2"를 가늠하면 "1/2"라고 하는 분수값이 나오게 됩니다. 그러므로 Smalltalk 에서 분수를 쓸 때에 다음의 관례를 따르게 되면 바탕글을 읽고 쓰는데 헷갈리지 않을 것입니다.


Smalltalk에서 분수를 나타내는 방법


  • 분자와 분모를 빗금표(/)로 나누어 쓰되 '/' 앞뒤에 빈 칸을 두지 않고 붙여서 쓴다.
  • 수식에서 분수가 등장하면 괄호를 두른다.


위의 규칙에 따라 식을 써 보면 아래와 같습니다.

1 + (2/3) * (3/4) + 1   9/4


이렇게 하면 Smalltalk는 괄호를 먼저 풀고 결과값으로 분수를 사용하게 될 것입니다.


그런데 이 앞에서도 이야기했듯이 나눗셈을 해서 남겨진 결과는 분수일 수도 있고 정수일 수도 있습니다. 그런데 이번에는 나눗셈을 한다는 생각을 하지 말고 분수를 쓴다는 기분으로 다음의 글토막을 가늠해 봅시다.

1/3   1/3
2/3   2/3
3/3   1
4/3   4/3
5/3   5/3
6/3   2


위의 식을 나눗셈 식이 아니라 한 덩어리의 분수라고 생각하면 재미있는 것을 발견할 수 있습니다. 바로 "약분"(約分, reducing)이 성립한다는 것입니다. 그럼 모든 경우에 있어서 분수 객체가 약분될 수 있는지 실험해봅시다. 다음의 글토막들을 하나씩 일터에 입력하고 펴 봅시다.

2/4   1/2
3/6   1/2
4/8   1/2
6/9   2/3
12/18  2/3
120/150  4/5
1024/8192   1/8
1024/8194  512/4097


어떻습니까? 신기하지 않습니까? 아무리 복잡한 수라 하더라도 필요하다면 약분을 합니다. 이렇게 분수(Fraction) 갈래에 속한 객체들은 분수에서 할 수 있는 모든 일을 다 합니다.


분수 객체는 정수 객체가 알아들었던 여러 가지 연산자인 '+', '-', '*', '/', '//', '\\', 'factorial', 'negated' 들을 모두 그대로 알아들을 수 있습니다.

(2/3) + (4/5)   22/15
(6/8) * (9/10)   27/40


그뿐이 아닙니다. 정수와 분수를 섞어서 계산할 수도 있습니다.

1 + (2/3)        5/3
(14/5) - 2       4/5
(6/7) * 3        18/7


이 경우 정수는 계산되기 전에 먼저 분수로 바뀌어집니다. 그런 다음 필요하면 통분을 통해서 계산을 하고, 가능하면 남은 결과를 약분 등을 통하여 간단하게 합니다. 한 마디로 말해서 Smalltalk는 초등학생이 하듯이 분수와 정수에 관한 문제라면 아무런 어려움 없이 모두 다 풀어낼 수 있는 특징을 가지고 있습니다. 물론 분수에 지시를 보낼 떼에는 반드시 나눗셈식을 괄호로 둘러주는 것을 잊으면 안되겠습니다.


그럼 분수 객체만 알아들을 수 있는 지시는 없을까요? 이를테면 분수에게 "분모는 뭐냐?"라던가 "역수를 한 번 구해봐라" 등과 같은 지시를 내릴 수 있을까요? 당연히 가능합니다. 분수 객체는 자신이 분수라는 것을 너무나 잘 알고있기 때무에 분수가 할 수 있는 모든 것을 할 수 있습니다. 다음의 글토막을 펴 보십시오.

(2/3) denominator        3            "분모 구하기"
(2/3) numerator          2            "분자 구하기"
(2/3) reciprocal         3/2          "역수 구하기"


분수 갈래에 속한 객체는 특별히 분모를 구하는 "denominator", 분자를 구하는 "numerator", 역수(逆數)를 구하는 "reciprocal"이라는 세 개의 지시를 알아들을 수 있습니다. 영어로 써서 어려운 것처럼 보이겠지만 처음 본 낱말들이라서 어렵게 보일 뿐입니다. 실제로 우리말로 하면 "분모", "분자", "역수"인데 어려울 게 무엇이겠습니까? :)


그런데 산수에서는 "모든 정수는 (정수/1)로 나타낼 수 있다"는 규칙이 있습니다. 즉 "6"은 정수이지만 분수로 생각하면 "6/1"이 된다는 것이지요. 그렇다면 정수 "6"에게 앞서 사용한 denominator, numerator, reciprocal 지시를 보내면 어떤 반응을 보일까요? 정수 객체가 위의 세 지시를 알아듣기나 할 수 있는 걸까요? 백견이 불여일행! 실제로 실행해 보면 알 수 있을 것입니다. 다음 글토막을 펴 봅시다.

6 denominator    1            "분모"
6 numerator      6            "분자"
6 reciprocal     1/6          "역수"


어떻습니까? 놀랍게도 보통의 정수 객체도 분수나 알아들을 수 있다고 생각했던 지시들에 알맞게 반응합니다. 이야! 똑똑하지 않습니까? 이와 같이 Smalltalk의 수 체계는 참으로 산수의 체계를 너무나도 충실하게 구현하고 있습니다. 물론 연산의 우선 순위는 빼야겠지만요. :)


그런데 앞에서 분수 객체에게 지시를 보낼 때에는 반드시 분수 객체를 괄호로 둘러싸라고 했는데, 만약 둘러싸지 않는다면 어떻게 될까요?

2/3 denominator          2
2/3 numerator            2/3
2/3 reciprocal           6


전혀 엉뚱한 값이 나왔습니다. 그럼 도대체 왜 저렇게 황당한 값이 나왔을까요? 그것은 바로 앞의 마디에서 설명한 "길표의 우선권"과 매우 깊은 관계를 가지고 있습니다. 위의 글토막들이 어떤 순서로 가늠되는지 스스로 살펴보시기 바랍니다.


물론 위의 경우는 그래도 결과값이 나타나지만, 아래의 경우는 더 심각합니다. 결과값은커녕 발자취 창이 나타나서 아예 아무것도 할 수 없다고 Smalltalk 가 투덜거리기 때문이지요.

2/3 class        "발자취 창 열림"


그러므로 다시 한 번 강조합니다. 분수 객체에게 어떤 지시를 내리고자 할 때에는 반드시 분수식을 괄호로 둘러주는 것을 잊지 맙시다.


때때로 계산을 하다 보면 분수보다는 소수를 포함한 수를 다루어야 할 필요가 생기게 됩니다. 특히 분모와 분자로 나타내기가 곤란한 무리수의 경우는 더욱 그렇습니다. 이럴 경우는 정수에 소수점(小數點)을 더하고, 소수를 함께 적는 표현 방법이 있습니다. 다음 글토막을 펴 보십시오..

0.1   0.1
-0.25   -0.25
1.23   1.23


위에서 가늠한 세 개의 글토막은 모두 소수입니다. "소수"(小數)란 0 보다는 크고 1보다는 작은 수를 의미합니다. 그렇기 때문에 "1.23"과 같은 수는 엄밀히 말하면 "정수 1에 소수 0.23을 더한 수"로 불러야 옳습니다. 그러나 보통 소수점을 가지고 있는 수를 소수라고 간단히 부르는 것이 일반적입니다.


바로 위에서 확인한 바와 같이 Smalltalk에서는 정수나 분수를 다룬 것처럼 소수 역시 다룰 수 있습니다. 정수를 쓰고 붙여서 바로 소수점을 찍어준 다음 소수점 아랫자리에 해당하는 수를 써주기만 하면 됩니다. 그리고 필요에 따라서 부호를 사용할 수도 있습니다. "-0.25"를 보면 알 수 있을 것입니다.


Smalltalk에서는 모든 것이 객체이므로 앞서 살펴본 정수와 분수는 모두 나름의 갈래(class)에 속해있었습니다. 그렇다면 소수 역시 어떤 갈래에 속해있을 것입니다. 객체에게 자신이 속한 갈래를 물어보는 길표가 "class" 라는 것쯤은 이제는 쉽게 아실 수 있어야 할터인데.... 어쨌든 아래 글토막을 펴보십시오.

0.1 class        Float
-0.25 class      Float
1.23 class       Float


어떻습니까? 각각의 소수들이 뭐라고 대답했습니까? 바로 "Float" 라고 대답했습니다. 그럼 도대체 이 "Float"란 어떤 것일까요? 사전에서 "Float"을 찾으면 "떠다니는"의 뜻을 가지고 있는데, 이는 컴퓨터 속에서 소수가 어떻게 표현되는 지와 관계가 있습니다. "class" 지시를 받은 소수 객체가 남긴 "Float"라는 말은 원래 "floating-point number"(부동소수점 수)라는 말을 줄인 것입니다. 여기서 부동(浮動)이라는 말은 "떠다닌다"는 뜻입니다. 따라서 "부동소수점 수"를 풀이하면 "점이 떠다니면서 나타나는 수" 라는 뜻인데, 이것은 컴퓨터 속에서 간단한 몇 개의 값만 바꾸어주는 것만으로 쉽게 소수점을 움직일 수 있다는 말입니다. 예를 들면 "0.1"을 "0.01" 이나 "1.0" 으로 만드는 것은 부동소수점수를 사용하는 컴퓨터에게는 매우 쉬운 일입니다.


여하튼 컴퓨터는 소수를 저장하기 위해서 부동소수점수를 사용하고, 이제 우리는 이것을 간단히 "소수"(Float)라 부르겠습니다.


자, 그럼 이 소수 객체, 즉 "Float" 갈래의 실체(instance)들은 도대체 어떤 지시를 알아들을 수 있을까요? Smalltalk 를 공부하면서 제가 늘 드리는 말씀! 바로 실험이 제일의 왕도(王道)라는 것입니다.


아래 글토막들을 펴 보고 어떤 결과가 나오는지 주시해보시기 바랍니다.

0.1 + 0.2                0.3
-1.2 - 0.3               -1.5
2.5 *  10.1              10.25
2.8 / 1.4                2.0
6.5 // 4.3               1
6.5 \\ 4.3               2.2
5.1 negated              -5.1
5.1 factorial            "발자취 창 열림"


결과를 살펴보았습니까? "Float" 갈래에 속한 객체들이 위에서 사용한 지시를 모두 다 알아들었습니까? 그렇지 않았을 것입니다. 앞서 우리가 살펴본 분수 갈래인 "Fraction"처럼 "factorial"을 제외한 다른 모든 길표를 알아들었습니다. 다시 한번 말하지만 "factorial"은 정수를 기반으로 하는 계산이기 때문에 분수나 소수에는 계승이라는 연산이 없습니다. 그러므로 분수나 소수 갈래에 속한 수들은 "Factorial"을 구하는 길수(method)를 모르니 우리가 내린 지시를 이해하지 못하는 것은 어쩌면 당연한 일입니다. 여하튼 소수에도 "factorial"을 제외한 모든 길표(selector)를 사용할 수 있다는 것을 기억하면 충분합니다.


그런데 앞서의 글토막을 가늠하는 중에 우리는 흥미로운 사실을 알아낼 수 있습니다. 바로 "2"와 "2.0"이 서로 같은지 다른지에 대한 문제입니다. 소수점이 붙은 정수와 그렇지 않은 정수를 Smalltalk는 어떻게 알아들을까요?


산수에서라면 "2.0"과 "2"는 같은 값을 가지는 수입니다. 그럼 Smalltalk 는 어떻게 생각하는지 물어보도록 합시다. Smalltalk에서 두 객체를 비교할 때에는 같음표(=)를 사용합니다.

2.0 = 2   true


"2.0"이 "= 2"라는 지시를 받아서 자신과 객체 "2"를 비교합니다. 두 객체는 모양이 다르지만 나타내는 값은 같기 때문에 당연히 위의 물음에 대해서 "맞다"(true)라는 대답이 나오겠지요.


그럼 "2"를 쓰는 곳에 "2.0"을 대신 쓸 수 있을까요? 직접 가늠해 봅시다.

2 factorial      4
2.0 factorial    "발자취창 열림"


"2"와 "2.0"에게 각각 계승을 구하라고 "factorial"지시를 보내주었습니다. 이 경우 정수 객체 "2"는 순순히 결과값을 내놓았는데, "2.0" 의 경우에는 "Float does not understand #factorial" 이라는 제목을 가진 발자취 창이 열렸습니다. 결국 Smalltalk는 "2.0"을 정수가 아닌 "소수"(float로 알아듣는다는 말인데....

2 class          SmallInteger
2.0 class        Float


분명히 두 객체는 서로 다른 갈래에 속해있습니다. 그렇기 때문에 같은 값이라 하더라도 "2.0"은 Smalltalk 입장에서는 정수가 아니기 때문에 길표 "factorial"을 알아들을 수 없었던 것입니다.


지금까지 우리가 나눗셈을 할 경우 결과는 항상 정수 아니면 분수였습니다. 그런데 나눗셈의 결과로 소수가 필요할 경우도 분명히 있습니다. 즉 필요하면 정수나 분수를 소수로 바꾸어주어야 할 때도 있다는 말입니다.


정수나 분수 갈래에 속한 모든 객체들은 "asFloat"이란 외마디 길표를 알아듣습니다. 여기서 "as~"란 "...로"라는 뜻으로, "asFloat"하면 "소수로" 정도의 뜻이 될 수 있습니다. 다음을 살펴봅시다.

2 asFloat                2.0
3 asFloat                3.0
(1/2) asFloat            0.5
(1/10) asFloat           0.1
(128/97) asFloat         1.31958763


정수나 분수 객체가 "asFloat" 지시를 받으면 자신의 값과 동일한 값을 나타내는 소수를 결과값으로 남기게 됩니다. 이 경우에도 분수를 표현하기 위하여 사용한 나눗셈 식은 괄호를 둘러주어야 합니다.


단순히 정수나 분수만을 소수로 바꿀 수 있는 것은 아닙니다. 아래와 같이 좀 더 복잡한 식의 결과 또한 소수로 바꾸어 나타낼 수 있습니다. 이 때 계산할 식 전체에 괄호를 둘러주어야만 "asFloat" 길표가 바르게 전달됩니다.

((3/2) * (2/7))  asFloat         0.428571429


이처럼 Smalltalk 에서는 필요에 따라 정수나 분수 객체를 소수로 바꾸어줄 수 있습니다. 그런데 왜 굳이 Smalltalk 가 정수와 분수를 사용하고, 소수의 경우는 특별한 지시를 통해야만 사용할 수 있도록 만들어 놓았을까요? 그것은 바로 "소수의 부정확성" 때문입니다.


소수가 얼마나 부정확한지 몇 가지 실험을 해 보겠습니다. 아래 글토막을 펴 보고 결과를 비교해 보십시오.

(1 - 0.9) * 2    0.2


위의 경우에는 제대로 결과가 나왔습니다. "1"에서 "0.9"를 빼면 "0.1", 여기에 다시 "2"를 곱하기 때문에 "0.2"라는 바른 결과값을 남겼습니다.


그러나 소수는 조금만 자릿수가 많아져도 계산에 착오를 일으킵니다.

(1 - 0.999999999999999999999999) * 2   0.0


어라? 0.0? 이게 어떻게 된 일일까요? 1 에서 0.999999999999999999999999 를 빼면 0.0000000000000000000000001 이 될 터이고, 여기에 2를 곱하면 당연하게 결과값으로 0.0000000000000000000000002 가 되는 것이 당연한데, 이건 뭔가 이상하다는 생각이 듭니다.


지금 보신 것이 소수의 취약점입니다. 컴퓨터는 정수를 계산할 때보다 소수를 계산할 때가 더 많은 기억 공간과 시간을 필요로 합니다. 따라서 컴퓨터가 처리할 수 있는 소수의 자릿수에도 자연히 제한이 가해집니다. 만약 계산의 중간값이나 결과값이 모두 정해진 자릿수 범위 안에서 처리된다면 문제가 없지만, 컴퓨터가 처리할 수 있는 자릿수 이상이 되어버리면, 계산에 착오를 일으키게 됩니다.


그럼 컴퓨터가 처리할 수 있는 부동소수점의 자릿수는 얼마나 될까요? 다음 글토막을 사용해서 알아봅시다. 마음대로 만들어낸 긴 소수 한 개와 원주율(phi)을 이용하면 다음과 같은 결과값을 얻을 수 있습니다.

3.1415926535898          3.14159265
1.23456789123456789      1.23456789


위의 결과로 미루어볼 때 Smalltalk에서는 대략 아홉 자리의 소수를 인식하고 처리할 수 있음을 볼 수 있습니다.


이 전에 가늠했던 다음의 식을 다시 한 번 생각해 봅시다.

(1 - 0.999999999999999999999999) * 2   0.0


위의 경우 계산에 사용된 소수의 자릿수는 15자리인데, Smalltalk 는 9 자리까지만 인식을 합니다. 그러므로 위와 같이 정밀한 계산은 착오를 일으키는 것입니다.


그렇지만 정수와 분수는 아무런 에누리없이 값을 처리하고 계산할 수 있습니다. 분수 역시 "분자"와 "분모"라는 두 개의 정수를 가지고 수를 표현하는 것이므로 자릿수에 제한을 받지 않는다는 것입니다. 실제로 다음의 글토막을 펴 보면...?

10 factorial / 100 factorial
1/2571820310955251121078572499345973889184192247144555265338209983884
96472644482792132224051962512451185663850090463028434334174412800000000
00000000000000


위와 같은 결과를 얻게됩니다. 즉 아무리 큰 수라고 하더라도 분수로 표현하게 되면 아무런 손실이 없이 계산을 할 수 있는 것입니다. 이러한 이유 때문에 Smalltalk에서는 될 수 있으면 부정확한 소수보다는 정확한 계산을 수행할 수 있는 분수를 사용하도록 권하고 있습니다.


다른 언어에서는...?


Smalltalk가 아닌 기존의 다른 언어들에서는 정수와 소수를 대상으로 연산을 합니다. 즉 나누어떨어지지 않는 나눗셈의 경우 결과값으로 소수를 사용합니다. 또한 대부분의 언어들은 분수를 기본 자료형으로 지원하지 않고 있으며, 분수가 필요하다면 따로 갈래(class)를 만들어서 사용해야 합니다. 물론 갈래를 만들 수 있고, 갈래에 딸린 연산자도 다중정의(overloading)할 수 있는 C++ 같은 언어를 사용해야하지만, 이 경우에도 역시 많은 제한이 따르게 됩니다. 기본적으로 이들 언어는 정수와 소수를 기본 연산으로 하고 있기 때문입니다.


이에 비해 Smalltalk는 매우 합리적인 선택을 했다고 볼 수 있습니다. 필요할 때에는 언제든지 분수를 소수로 바꿀 수 있기 때문에, 굳이 연산의 정확도가 떨어지는 소수를 연산 대상에서 제외한 것은 상당히 합리적이라고 필자는 생각합니다.


물론 계산이 부정확하게 된다는 단점은 있지만, 실제로 소수를 사용하여 문제를 해결해야 할 필요성이 있기 때문에 여기서는 소수의 표현 방법에 대해서 좀 더 짚고 넘어가도록 하겠습니다. 소수를 표현할 때에는 두 가지의 서로 다른 방법을 사용할 수 있습니다. 하나는 지금까지 우리가 사용해 온 방법이고, 나머지 하나는 "공학적 표기법", 또는 "멱법"(冪法)이라고 하는 것인데, 여기서는 이 방법을 알아보도록 하겠습니다.


자릿수가 많은 소수를 표현 할 때 우리는 다음과 같은 표현방법을 씁니다.

1x10^-20
2x10^-3 
3x10^3
4x10^20         (단, '^' "제곱').


즉 자릿수를 나타내는 "0"을 늘어놓는 것이 아니라 수를 간단히 하여 여기에 "10"을 밑으로 하는 멱수를 곱해서 자릿수를 나타내는 방법입니다. 이를 이용하면 "0"이 줄줄이 늘어서 있기 때문에 생기는 혼란을 막을 수 있습니다. Smalltalk에서 공학적 표기법을 사용하려면 다음과 같이 합니다.

1e-20
2e-3
3e3
4e20


위에서 "e"는 "10의 거듭제곱"을 뜻합니다. 그러므로 아래의 세 개의 값은 모두 같은 의미입니다.

2e3
20e2
2000e1
2000


공학적 표기법을 좀 더 쉽게 이해할 수 있는 두 가지 관점이 있습니다.


먼저 수학적인 방법입니다. 여기서는 앞서 이야기했듯이 "e"를 "10의 x승"으로 생각하여 계산하면 매우 쉽게 읽을 수 있습니다.

0.0314159e2 = 0.0314159 * 10^2 = 0.314159 * 100 = 3.14159
314.159e-2 = 314.159 * 10^-2 = 314.156 / 10^2 = 314.159 / 100 = 3.14159


다음으로는 수학적 방법이 아닌 좀 더 간단하고 직관적인 방법이 있습니다. 이 방법은 소수점을 "e" 뒤에 있는 숫자만큼 옮기라는 것입니다. 즉 "e" 다음의 수가 양수이면 소수점을 오른쪽으로, 음수이면 왼쪽으로 옮기면 원래의 값을 계산할 수 있다는 것입니다.

0.0314159e2 = "소수점을 오른쪽으로 두 칸 옮긴다." = 3.14159
314.159e-2 = "소수점을 왼쪽으로 두 칸 옮긴다." = 3.14159


앞서 우리는 정수와 분수와의 관계를 알아보았습니다. 정수와 분수가 하나의 계산식 안에 섞여있으면, 계산을 하기 전에 정수가 우선 분수로 바뀌어지는 것을 볼 수 있었습니다. 그럼 정수와 분수, 그리고 소수가 함께 섞여있으면 Smalltalk는 어떤 반응을 보일까요?


다음의 글토막을 가늠해 보십시오.

1 + (1/2) + 0.3   1.8


위의 식을 Smalltalk는 다음처럼 가늠합니다.

1 + (1/2) + 0.3 
1.0 + 0.5 + 0.3 
1.8


위의 결과로써 우리는 Smalltalk가 정수, 분수, 소수가 섞여있는 식을 어떻게 계산하는지를 알 수 있게 됩니다. 정수, 분수, 소수가 식에 함께 섞여 있을 때는 다음과 같은 순서로 변환이 일어납니다.


정수 → 분수 → 소수


그래서 결국 계산식에 소수가 섞여있으면 전체 식을 계산한 후 남는 결과값 역시 소수가 된다는 것을 기억해 두어야 할 것입니다. Smalltalk에서는 언제나 수에 대해서 위와 같은 변환 규칙이 적용되기 때문입니다.


그런데 계산을 하다보면 분수나 소수를 정수로 바꾸어주어야 할 때가 있습니다. 이럴 경우 표현하고자 하는 소수에 소수점 아래 자릿수가 0이면 괜찮지만, 소수점 아래에 숫자가 있게 되면 자연히 이런 소수는 잘라버려야 합니다. 왜냐하면 정수는 소수점이 없는 수이기 때문이지요. 소수나 분수를 정수로 만드는 방법에는, 소수점 아래 숫자를 무조건 잘라내는 방법과 반올림을 하는 방법 두 가지가 있습니다.


소수점 아래 숫자를 무조건 잘라버리는 지시는 "truncated"이며, 반올림을 하라는 지시는 "rounded"입니다. 이 두 가지 지시는 분수(Fraction)와 소수(Float) 객체가 모두 알아들을 수 있습니다.

5.4 truncated    5
5.7 truncated    5
6.3 truncated    6
8.8 truncated    8


위의 네 경우 모두 소수값에 관계 없이 소수 아랫자리의 수는 모두 버려졌습니다. 이것이 "truncated" 지시의 결과입니다. 반면 "rounded"지시는 다릅니다. 다음을 가늠해 보십시오.

5.4 rounded      5
5.7 rounded      6
6.3 rounded      6
8.8 rounded      9


"rounded"지시는 소수의 값에 따라 반올림을 합니다. 즉 소수의 값이 0.5보다 작으면 버리고, 0.5 보다 크면 버린 값에 1 을 더합니다. 그래서 "5.4"와 "6.3"의 경우는 각각 "5"와 "6"이 되었고, "5.7"과 "8.8"의 경우는 버린 값에 1이 더해져서 "6"과 "9"가 되었던 것입니다.


분수에 있어서는 조금 다르게 생각해 보아야 합니다. 일단 분수에 앞서 설명한 "truncated"와 "rounded"지시를 적용하려면 계산할 연산수가 가분수(假分數)여야 하며, "truncated"와 "rounded"는 주어진 가분수를 대분수(帶分數)로 바꾸어서 계산을 수행합니다. 다음 글토막을 펴 보십시오.

(5/4) truncated          1
(7/4) truncated          1


가분수 "5/4"를 대분수로 고치면 "1과 1/4"가 되고, "7/4"을 대분수로 고치면 "1과 3/4"가 됩니다. 이 대분수에서 정수부분이 한결같이 "1"이기 때문에 "truncated"지시는 분수 부분을 잘라버리고 정수 부분만 결과값으로 남기는 것입니다.


이제 "rounded"를 설펴봅시다.

(5/4) rounded    1
(7/4) rounded    2


"rounded"는 분수 부분의 분자가 분모의 절반을 넘게 되면 정수 부분에 1 을 더한 다음 분수 부분을 버리고, 만약 분수 부분의 분자가 절반에 미치지 못하면 분수 부분만 버립니다. 위의 경우 "5/4"는 "1과 1/4"인데, 분수 부분의 분자가 "1", 즉 분모 "4"의 절반이 아니므로 정수 부분 "1"을 남겼지만, "7/4"의 경우 대분수로 고치면 "1과 3/4"가 되고, 분자 "3"이 분모 "4"의 절반이 넘으므로 정수 "1"에 하나를 더하여 "2"를 결과값으로 남깁니다.


이처럼 분수나 소수를 정수로 바꾸고자 할 경우에는 원래의 값이 손실될 수밖에 없다는 것을 기억하고, 어떤 경우에 버릴 것인지, 아니면 반올림을 할 것인지를 신중하게 생각해 보는 것이 중요합니다.


지금까지 우리는 분수와 소수에 대해서 여러 가지의 성질을 알아보았습니다. 그러면 마지막으로 분수와 소수가 여태껏 우리가 알아낸 갈래 씻줄(class hierarchy)과는 어떻게 얽어져 있는지 알아봅시다.


우리가 정수 갈래의 씻줄을 알아내기 위해서 "class"와 "superclass"라는 두 개의 지시를 사용했듯이, 분수와 소수에도 똑같은 방법을 사용해 봅시다. 아래에 있는 글토막들을 하나씩 차례로 가늠해 봅시다.

(1/2) class                      Fraction
Fraction superclass              Number
Number superclass                ArithmeticValue
ArithmeticValue superclass       Magnitude
Magnitude superclass             Object


분수(Fraction)는 바로 수(Number)에 속해있습니다. 그리고 Number 갈래는 앞에서 우리가 살펴보았듯이 산술값(ArithmeticValue)과 크기값(Magnitude)에 딸려있으며, 결과적으로는 객체(Object)에 얽혀있습니다. 그러므로 결국 분수 갈래는 Number 갈래에 딸려있다고 보면 됩니다.


이번에는 소수(Float)입니다.

3.5 class                        Float
Float superclass                 Number
Number superclass                ArithmeticValue
ArithmeticValue superclass       Magnitude
Magnitude superclass             Object


소수 역시 분수와 똑같은 씻줄을 가지고 있습니다. 즉 Float(소수) 갈래는 분수(Fraction)와 같이 Number(수)에서 갈라져 나온 아랫갈래인 것입니다. 그러므로 정수(Integer), 분수(Fraction), 소수(Float) 모두 Number(수)라는 갈래에서 갈라져 나온 셈입니다.


그럼 지금까지 우리가 알아낸 갈래씻줄을 그림으로 그려봅시다.

그림 2-3::Number 갈래를 포함한 갈래씻줄 (stp2-3.gif)


위에서 알수 있듯이, 정수, 분수, 소수는 모두 수(Number)라는 갈래에 속해있으므로, 우리들은 Smalltalk 에서의 수에 대한 것은 거의 다 공부했다고 볼 수 있습니다.


정수와 분수, 그리고 소수는 수를 구성하는 중요한 갈래이며, 각 갈래들의 실체(instance)는 독특한 성질을 가지고 있습니다. 이들 성질을 제대로 간파하여 이용하는 것이 수를 잘 다룰 수 있는 길이 될 것입니다. 결국 모든 것이 객체인 Smalltalk에서는 각 객체의 특성은 무엇이고, 각 객체가 알아들을 수 있는 지시에는 어떤 것들이 있으며, 그 객체들이 속한 갈래는 다른 갈래와 어떤 씻줄로 얽혀있는지를 파악해야만 제대로된 Smalltalk 프로그램을 작성할 수 있는 것입니다.


객체의 안쪽

지금까지 우리는 여러 가지 객체와 그들이 속한 갈래를 알아보았습니다. 대부분 우리들이 알아본 객체는 수(Number)에서 출발하는 것들이었으며, 정수(Integer), 분수(Fraction), 그리고 소수(Float)의, 세 개의 갈래가 Number 갈래에 씻줄로 얽혀있었습니다. 이런 것들은 모두 객체의 표면적인 것들이었습니다.


이제부터 우리는 객체의 안쪽으로 깊이 들어가 보겠습니다. 우리들이 눈으로 보고 지시를 주고받음으로써 대화를 나눈 객체들의 안쪽은 어떻게 생겼을까요? 그리고 또한 그런 객체들은 어떤 성분으로 이루어져있을까요? 이제 우리는 그러한 객체의 내적인 면에 대해서 알아볼 것입니다. 그래서 이 마디를 끝내고 나면 여러분은 객체의 속이 어떻게 이루어져 있으며, 또한 이것들이 어떻게 유기적으로 연결되어 객체를 구성할 수 있는지에 대한 줄거리를 잡을 수 있을 것입니다.


먼저 이러한 객체의 안쪽을 들여다보기 위하여 우리는 "객체 탐색기"의 사용법부터 공부해야 합니다.


탐색기 사용하기

모든 객체는 "inspect"라는 지시를 알아듣습니다. 사전에서 "inspect"를 찾아보면 "면밀하게 살피다, 검사하다, 점검하다 검열하다, 탐색하다" 등의 뜻을 가지고 있습니다. 즉 "inspect" 지시를 받은 객체는 자신의 속을 들여다볼 수 있도록 열어줍니다. 이 때 나타나는 창이 바로 "객체 탐색기"(object inspector)입니다.


다음 세 개의 글토막들을 모두 가늠해 보십시오.

27 inspect 
(1/3) inspect 
32.1 inspect


그러면 다음 그림 2-4 와 같이 세 개의 객체 탐색기를 볼 수 있을 것입니다. 이렇게 해서 열린 탐색기는 여러분이 닫을 때까지 남아있게 됩니다.

그림 2-4::객체 탐색기가 열린 모습 (stp2-4.gif)


그림에서 볼 수 있듯이 객체 탐색기는 크게 두 널(pane)로 되어있습니다. 왼쪽 널에는 "self"에서부터 시작해서 "numerator", "denominator", "1", "2" 등과 같은 선택 가지들이 있고, 오른쪽 널에는 무언지는 모르지만 어떤 값이 나와있습니다. 그럼 도대체 각각의 널에 표시된 것은 무엇을 의미하는 것일까요?


먼저 왼쪽 널에 있는 "self"부터 살펴봅시다. "self"가 무슨 뜻입니까? 바로 "자기 자신"이란 말입니다. 탐색기에서의 "self"는 현재 탐색하고 있는 객체 자체를 의미합니다. 세 개의 탐색기 모두에서 "self"가 현재 선택되어있음을 알 수 있습니다.


이 상태에서 오른쪽 널을 보십시오. 어떤 결과가 나타났습니까? "27", "1/3", "32.1", 바로 탐색하고 있는 객체가 그대로 표시되었습니다.


여기서 오른쪽 널의 기능을 확실히 알 수 있습니다. 바로 왼쪽 널에서 선택된 항목의 값을 오른쪽 널에 나타낸다는 것입니다.


그럼 이제 "1/3"을 탐색하고 있는 "Inspecting a Fraction" 창을 살펴봅시다. 왼쪽 널에 무엇이 있습니까? "numerator" 와 "denominator" 가 있습니다. 이것은 무엇을 말합니까? 바로 "분자"와 "분모"를 나타냅니다. 그럼 이제 한 번 "numerator"를 선택해서 돋이되게 만들어 봅시다. 화면에 어떤 결과가 나타났습니까? 오른쪽 널에 있던 "1/3"이 없어지고 "1"이라는 값으로 바뀌어 있을 것입니다. 그럼 "denominator"는 어떨까요? 역시 왼쪽 널에서 분모를 나타내는 "denominator"를 선택하면 오른쪽 널에 "3"이 나타날 것입니다. 지금 이야기한 것을 그림으로 정리하면 다음과 같습니다.

그림 2-5::객체 탐색기의 왼쪽 널과 오른쪽 널의 관계 (stp2-5.gif)


말로 풀어 놓았기 때문에 어렵게 들릴지 모르겠지만, 실제로 탐색기를 몇 번 움직여 보면 매우 직관적으로 동작한다는 것을 알 수 있습니다. 탐색기의 왼쪽 널은 객체와 그 객체를 이루고 있는 성분(part)들을 나타냅니다. 그리고 오른쪽 널에는 현재 선택된 성분이 가지는 값이 표시됩니다.


그럼 여기서 "27"의 경우는 왼쪽 널에 "self" 만 나타났기 때문에, "27"이, 아니, 좀 더 크게 생각하여 SmallInteger 갈래에 속한 모든 객체는 성분이 없는 것일까 하는 의문을 가져볼만 합니다. 만약 이런 생각을 가진 사람이라면 이제 슬슬 객체에 대해서 어느 정도 눈을 떴다고 해도 좋을 것입니다.


그렇습니다. SmallInteger 갈래의 실체는 성분을 가지지 않습니다. 이것은 물리에서 수소(H)나 산소(O) 등의 원자는 그들을 이루는 성분을 가지고 있는 것이 아니라, 그들 자체로써 완전히 존재하는 것과 같습니다. Smalltalk 의 객체에도 이렇게 성분을 가지는 객체와 그렇지 않은 객체가 있는데, 객체의 성분에 대해서는 조금 있다가 알아보도록 하겠습니다. 일단 지금 중요한 것은 그러한 객체의 내부를 들여다볼 수 있는 탐색기의 사용법을 익히는 것이기 때문입니다.


여태까지 우리는 객체에게 "inspect"란 지시를 보냄으로써 탐색기를 열었지만 앞("1.5.3", "2.1")에서 우리는 객체 탐색기를 여는 다른 방법을 이미 알아본 적이 있습니다. 기억하십니까? 네, 바로 글토막을 가늠할 때 탐색기를 여는 방법이었습니다.


어떤 글토막을 가늠할 때 Workspace > Inspect It 메뉴를 사용하던지 단축글쇠인 Ctrl-I 를 사용하면 글토막이 가늠된 결과로 남은 객체를 탐색해볼 수 있다고 했습니다. 그러므로 객체에게 "inspect" 지시를 내리는 것과 해당 객체에 덩이(block)를 씌워 Ctrl-IWorkspace > Inspect It 을 사용하는 것은 똑같은 결과를 나타냅니다.


탐색기는 객체의 내부를 세밀하게 조사하는데 사용하는 도구답게 "연계 탐색기능"(drill down inspecting)이라는 것을 가지고 있습니다. 앞에서 객체는 자신을 구성하고 있는 성분을 가진 것과 그렇지 않은 것이 있다고 했는데, 연계 탐색 기능은 어떤 객체의 성분까지 탐색해 볼 수 있는 기능을 말합니다. 방금 열어놓았던 세 개의 탐색기 중에 "1/3"을 탐색하고 있는 "Inspect a Fraction" 이라는 제목이 달린 창을 살펴봅시다. 왼쪽 널에는 "self", "numerator", "denominator"라는 세 개의 선택 가지가 있습니다. 이 세 개의 가지들 중에 "numerator" 가지를 돋이되게 만든 다음, 메뉴의 Workspace > Inspect It 을 실행시하거나 Ctrl-I를 눌러봅시다. 어떻게 되었습니까?

그림 2-6::연계 탐색 기능을 통해 열린 탐색기 (stp2-6.gif)


"Inspecting a SmallInteger" 라는 또 하나의 탐색기가 열렸습니다. 새로 열린 탐색기는 어떤 객체를 보여주고 있습니까? 네, "1" 입니다. 이 "1" 은 어디서 나온 것이겠습니까? 그렇죠. 바로 "1/3" 객체의 분자(numerator)입니다.


이렇게 탐색기의 왼쪽 널에 표시된 객체의 성분 중에서 하나를 돋이되게 받는 다음 Ctrl-IWorkspace > Inspect It 메뉴를 실행하면 돋이된 가지의 객체에 대해서 다시 탐색기가 열리게 됩니다. 이와 같이 어떤 객체를 구성하고 있는 성분을 다시 탐색해 들어가게 되면 결국에는 자기 자신이 성분인 객체―이를 '홀객체'라 하는데 이는 잠시 뒤에 설명하겠습니다.―까지 내려가게 됩니다. 이 연계 탐색 기능을 이용함으로써 객체의 안쪽을 속속들이 살펴볼 수 있는 것이지요.


위에서 소개한 방법 말고도 연계 탐색 기능을 동작시킬 수 있는 방법이 하나 더 있는데 ,그것은 왼쪽 널에 표시된 선택 가지를 두 번(double click) 누르는 것입니다. 보통은 이 방법이 간편하기 때문에 많이 사용됩니다. 아래에 연계 탐색기능을 동작시키는 방법을 정리해 두겠습니다.


연계 탐색기능 사용하기


  • 탐색하고자 하는 성분을 돋이되게 만든 후 Workspace > Inspecting It 메뉴를 사용한다.
  • 탐색하고자 하는 성분을 돋이되게 만든 후 <Ctrl-I> 글쇠를 누른다.
  • 탐색하고자 하는 객체의 성분을 두 번 누른다.


이러한 연계 탐색은 구조가 매우 복잡한 객체를 탐색하는데 매우 편리합니다. 지금은 별로 사용할 일이 없겠지만, 나중에 여러분이 복잡한 객체를 만들고 그 내용을 탐색할 때에 이 기능을 사용하게 될 것입니다.


이제 여러분의 화면에는 많은 탐색기들이 떠 있을 것인데, 이들을 하나도 남김 없이 다 닫아주십시오. 이제부터 탐색기의 또 다른 기능을 공부해야 하는데, 너저분한 책상에서 시작할 수는 없지 않겠습니까? ^^:


이제 다음 글토막을 탐색해서 새로운 객체 탐색기가 열리도록 합시다.

2/3


자, 다시 한 번 탐색기를 유심히 살펴봅시다. 지금까지는 탐색기의 왼쪽 널을 주로 살펴보았는데, 이제부터는 탐색기의 오른쪽 널을 살펴볼 것입니다. 오른쪽 널을 주의 깊게 살펴보십시오. 뭔가 느껴지는 것이 없습니까? 자, 지금 탐색기 옆에 있는 일터(workspace)와 가만히 비교해 보십시오. 뭔가 똑같은 것이 있습니다. 찾을 수 있겠습니까?

그림 2-7::탐색기와 일터 (stp2-7.gif)


네, 이미 눈치 챈 분들도 있을는지 모르겠습니다. 바로 일터의 화면 바탕색과 탐색기의 오른쪽 널의 바탕색이 같다는 것입니다. Dolphin Smalltalk를 만든 사람이 왜 이렇게 일터와 탐색기의 오른쪽 널을 같은 색깔로 만들었을까요? 단순한 미적 감각일까요? 그렇지 않습니다. 여러분이 지금 보고 있는 탐색기의 오른쪽 널은 일터가 할 수 있는 일을 모두 할 수 있기 때문에 그것을 보여주기 위해서 일부러 바탕색을 같게 만들어 놓은 것입니다.


어라? 그렇다면 탐색기의 오른쪽 널을 일터로 쓸 수 있다는 말이라면, 정말 그런지 그렇지 않은지 실험해 봐야 할 일입니다. 앞서 "2/3"를 탐색해서 열린 탐색기의 오른쪽 널을 보십시오. 지금 왼쪽 넓에는 "self"가 돋이되어 있고, 오른쪽 널에는 "self"의 값인 "2/3"이 표시되어있을 것입니다. 이제 오른쪽 널에 있는 "2/3"을 "(2/3) class"처럼 고친 뒤에 방금 고친 글토막을 펴 보십시오. 어떻게 되었습니까?

그림 2-8::일터로 사용된 탐색기의 오른쪽 널 (stp2-8.gif)


그림에서 볼 수 있듯이 일터에서 했던 것과 똑같은 결과로 "Fraction" 이란 값이 덩이를 쓴 체로 나타났습니다. 이상의 결과들을 미루어볼 때 탐색기의 오른쪽 널은 일종의 일터로 생각해도 좋을 것입니다.


그런데 왼쪽 널에는 "self"가 돋이되어있습니다. 따라서 지금 우리가 오른쪽 널에 무언가를 입력했다면 왼쪽 널에 있는 "self"에 어떤 영향을 주었을지도 모르는 일입니다. 자, 잠시 "self" 아래에 있는 "numerator"를 돋이되게 만든 다음 다시 "self"를 돋이되게 만듭시다. 어떻게 되었습니까? "self" 에는 아무런 변화가 없는 것입니다.


즉 우리는 탐색기의 오른쪽 널을 자유롭게 일터로 삼아 여러 가지 글토막을 가늠해볼 수 있습니다. 그렇지만 우리가 탐색기에서 내놓은 값들을 가지고 여러 가지 일을 한다고 해서 탐색기의 원래 값이 변하는 일은 없습니다. 따라서 탐색기의 오른쪽 널은 여러분이 사용하고싶은 대로 마음껏 써도 좋다는 결론이 나옵니다. 이것은 Smalltalk에서 객체의 속을 들여다보고 문제를 해결하는데 있어서 매우 뛰어난 기능을 제공합니다.


이제 탐색기가 할 수 있는 일 한 가지를 마지막으로 더 알아보고 이번 나눔을 닫을까 합니다.


우리는 지금까지 탐색기가 보여준 객체의 값을 확인하기만 했습니다. 그렇지만 필요에 따라서 우리는 이러한 객체들의 값을 바꾸어줄 수 있습니다. 그럼 이제 탐색기가 표시하는 객체의 값을 어떻게 바꾸어줄 수 있는지 실험을 통해서 알아봅시다.


지금 여러분 화면에는 객체 "2/3" 의 값을 담은 탐색기가 열려있을 것입니다. 여기서 선택 막대를 "denominator"에 위치하여 오른쪽 널에 "2/3"의 분모인 "3" 이 표시되도록 만듭시다. 이제 오른쪽 널에 있는 "3"을 지우고 "4" 를 써넣습니다. 이렇게만 하면 "2/3"의 값을 "2/4"로 바꿀 수 있을까요?


그렇지 않습니다. 바로 앞에서 살펴보았듯이, 탐색기의 오른쪽 널은 일터처럼 자유롭게 값을 지우기도 하고 쓸 수도 있다고 했습니다. 따라서 지금과 같은 상황에서 선택 막대를 "self"나 "numerator" 처럼 다른 가지로 옮기게 되면 우리가 방금 써 넣은 "4"라는 값은 지워지고 맙니다. 그렇기 때문에 방금 우리가 써넣은 것을 현재 돋이된 성분의 값으로 바꾸라는 지시를 특별히 탐색기에게 내려줄 필요가 있습니다.


오른쪽 널의 "3"을 "4"로 바꿔준 후에 Workspace > Accept 나 글쇠판의 Ctrl-S 를 눌러봅시다. 화면에는 아무런 변화가 없어 보입니다. 그렇지만 여러분이 Workspace > Accept 메뉴를 실행하거나 Ctrl-S 글쇠를 눌렀을 때 "denominator"는 이미 "3"에서 "4"로 바뀌어 있습니다. 확인하기 위해서 "self"를 다시 한 번 돋이되게 해 봅시다. 오른쪽 널에 무엇이 나타났습니까? 바로 "2/4"가 나타났습니다. 우리가 앞에서 한 조작으로 인해서 "2/3" 이었던 객체가 "2/4"로 바뀐 것입니다.


이렇게 탐색기를 이용하여 값을 탐색하다가 어떤 성분의 값을 바꿀 필요가 있다면, 먼저 값을 바꿀 성분을 돋이되게 만든 다음 성분의 값이 표시된 오른쪽 널에 원하는 값을 넣고 Workspace > Accept 메뉴를 실행하거나 글쇠판에서 Ctrl-S 글쇠를 누르면 돋이된 성분의 값이 바뀌게 됩니다.


Smalltalk에서 분수를 다룰 때는 언제나 기약분수(旣約分數)의 형태를 취하게 됩니다. 이는 분수의 분자나 분모가 바뀌면 분수 객체가 스스로 알아서 약분을 해 주기 때문입니다. 그러나 탐색기에서 분자나 분모의 값을 바꾼 경우 분수 객체는 값이 바뀌었다는 사실을 통보 받지 못합니다. 즉 우리가 탐색기에서 어떤 값을 바꾸더라도 실제로 값이 바뀐 객체는 자신의 값이 바뀌었다는 것을 직접적으로 알 수 없습니다. 따라서 "2/4"는 약분이 되지 않은 상태로 존재하게 되는 것이지요.


객체 성분의 값을 바꿀 때에 Smalltalk의 글토막을 사용할 수 있습니다. 앞에서 우리는 "3"을 "4"로 바꾸기 위해서 탐색기의 오른쪽 널에 직접 "4"라는 값을 적어 넣었으나, 이 자리에 Smalltalk에서 사용할 수 있는 글토막을 적어넣을 수 있다는 말이 됩니다.


실험을 해 봅시다. 열려있는 탐색기에서 다시 "denominator"를 돋이되게 만듭니다. 오른쪽 널에는 방금 우리가 바꾼 "4"라는 값이 나타나 있을 것입니다. 이 상태에서 이미 있던 "4"를 지우고 다음과 같은 글토막을 오른쪽 널에 적어 넣습니다.

3 factorial


이 상태에서 Workspace > Accept 메뉴를 실행하거나 글쇠판의 Ctrl-S 글쇠를 눌러봅시다. 어떻게 되었습니까? 글쇠를 누르자마자 "3 factorial" 이라는 글토막이 "6"으로 바뀌었습니다. 이렇게 어떤 객체의 값을 바꾸기 위해서는 단순히 값을 써 줄 수도 있고, Smalltalk가 가늠할 수 있는 글토막을 쓸 수도 있습니다.


이제까지 우리는 객체 탐색기에 대한 사용법을 알아보았습니다. 탐색기는 기본적으로 어떤 객체의 값을 자세하게 살펴볼 수 있었으며, 그 객체의 값을 바꿀 수도 있었습니다. 더불어 탐색기는 일터의 역할까지 겸할 수 있다는 것도 알아보았습니다. 이제 여기서 공부한 것들이 바로 다음에 이어질 객체의 성분을 알아보는 데에서 큰 도움이 될 것입니다.


다른 언어에서는...?


요즘 출시되는 모든 개발 도구들은 자체적으로 객체의 내부를 탐색해 볼 수 있는 탐색기를 준비하고 있습니다. 이는 모두 Smalltalk의 영향을 받은 것입니다. 필자가 탐색기를 처음 본 것이 "Turbo C++ 1.0"이었을 것입니다. 여기서는 벌래잡이(debugging) 시간에 탐색기를 사용할 수 있었습니다. Turbo C++ 의 탐색기 역시 Smalltalk 의 탐색기처럼 연계 탐색 기능을 지원했습니다. 처음 필자가 이 기능을 보고는 상당히 대단하다는 생각이 들었었지요. 물론 그 전의 "Turbo C 2.0" 에서도 "Watch"나 "Evaluate/Modify"라는 기능이 있어서 객체(정확히는 "변수")의 값을 살펴보거나 바꿀 수 있었습니다. 그러나 객체와 갈래의 개념을 지원하는 C++ 언어에서 동작하는 탐색기보다는 기능이 떨어졌습니다.


세월은 흘러 이제는 시각적(visual) 개발 도구가 나왔고, 이 도구들은 실행시간이나 벌레잡이 시간에 객체의 상태를 검사하고 값을 바꿀 수 있는 탐색기를 갖추었습니다. 더욱이 창이나 객체를 설계할 때(designe time) 값을 변경하여 화면에 그 결과가 바로 반영되는 기능을 모두 다 구현하고 있습니다. 결국 이것은 모두 Smalltalk에서 10년도 훨씬 전에 개발되어있었습니다. 더욱이 Smalltalk의 탐색기에서는 Smalltalk가 실행할 수 있는 글토막을 기반으로 객체의 값을 바꿀 수 있었습니다. 그러므로 다른 언어에서 지원하는 탐색기보다 Smalltalk의 탐색기는 한 차원 더 높은 기능을 제공한다고 해도 과언이 아닐 것입니다. 물론 겉으로 보기에 Smalltalk 의 탐색기는 다른 개발도구의 탐색기보다 초라해 보이겠지만 말입니다.


객체의 성분

바로 앞 나눔에서 우리는 객체 탐색기에 대해서 알아보았고, 몇 가지 실험을 통해서 탐색기에 익숙해졌을 것입니다. 지금부터는 객체를 구성하고 있는 성분에 대해서 조금 더 자세히 알아보겠습니다. 그러면서 우리는 수(數)가 아닌 다른 갈래에 속해 있는 객체들도 다루어보게 될 것입니다.


먼저 다음 세 개의 객체를 탐색해봅시다. 다음의 글토막을 Ctrl-I 글쇠를 눌러서 가늠하거나, 객체에게 "inspect" 지시를 보내면 앞서 보았던 그림 2-4 와 같이 세 개의 탐색기가 열릴 것입니다.

27
1/3
32.1


탐색기의 왼쪽 널(pane)을 주의깊게 살펴봅시다. 정수 "27"의 경우는 "self" 라는 선택 가지 하나만 있고, 분수 "1/3"의 경우는 "self" 선택가지와 더불어 "numerator", "denominator"라는 선택 가지가 있습니다. 소수 "32.1"의 경우는 "self"와 함께 "1"부터 "8"까지의 여덟 개의 선택 가지가 표시되고 있습니다.


앞에서도 이야기한 것과 같이 탐색기의 왼쪽 널에는 현재 탐색하고 있는 객체 자신(self)과 함께 그 객체의 성분이 표시된다고 했습니다. 따라서 정수 "27"의 경우는 아무런 성분을 가지지 않고 있음을 알 수 있습니다. 반면 "1/3"과 "32.1"의 경우는 나름으로의 성분을 가지고 있습니다.


Smalltalk의 객체는 크게 성분을 가지지 않는 "홀객체"와 하나 이상의 성분으로 이루어져 있는 "핫객체"로 나누어볼 수 있습니다. 과학에서도 더 이상 쪼갤 수 없는 원자가 있는가 하면 여러 가지 성분으로 하나의 물체를 이루는 것들도 있습니다. Smalltalk에서도 객체 자체로써 존재하는 것들이 있는가 하면 하나 이상의 성분으로 구성된 객체가 있는 것입니다.


위의 결과를 보면 정수, 정확히 말하면 작은 정수(SmallInteger) 갈래에 속한 실체들은 성분을 가지지 않으며, 분수와 소수의 경우는 성분을 가진다는 것을 발견할 수 있습니다.


그런데 이러한 객체를 이루는 성분에도 두 가지 종류가 있습니다. 먼저 분수 "1/3"을 관찰해 봅시다. 분수의 경우는 분자를 나타내는 "numerator"와 분모를 나타내는 "denominator" 라는 두 개의 성분이 있습니다. 이 성분에는 이름이 붙어 있습니다. 이렇게 객체의 성분 중에 이름이 붙은 성분을 "이름 붙은 성분"(named part)이라고 부릅니다. 반면 "32.1"과 같은 소수 객체는 "1" 부터 "8" 까지 여덜 개의 성분이 존재합니다. 이들 성분에는 나름대로 번호가 붙어 있는데, 이렇게 객체를 이루는 성분에 번호가 붙은 것을 "번호 붙은 성분"(indexed part)이라 부릅니다.


이름붙은 성분의 경우 대부분 우리는 이 성분이 무엇을 의미하는지를 쉽게 알 수 있습니다. "1/3" 객체의 "numerator"는 분자 "1"을 가리킨다는 것을 우리는 쉽게 알 수 있습니다. 반면 "32.1"을 구성하고 있는 여덜 개의 번호 붙은 성분을 탐색기를 이용하여 탐색해 봅시다. 전혀 알 수 없는 값으로 이루어져 있습니다. 이는 번호 붙은 성분의 경우는 객체 자체를 컴퓨터가 처리하기 쉽게 분해하여 저장해 놓기 때문입니다. 실제로 소수의 경우는 여덜 개의 바이트(byte)로 이루어진 객체입니다. 바이트는 컴퓨터에서 정보를 기억시킬 때 기본 단위로 사용하는 것인데, 주로 번호 붙은 성분의 경우는 이렇게 객체를 바이트별로 분해해서 저장해 놓습니다. 각각의 바이트에 들어있는 값이 무엇을 의미하는지, 그리고 또한 이런 바이트와 정보 저장의 관계는 무엇인지 등은 지금 당장은 알아둘 필요가 없습니다만, 컴퓨터 프로그래밍의 깊은 곳을 공부한다거나 컴퓨터 구조를 알아야 하는 경우에는 필요할지도 모르는 기법입니다. 아무튼 Smalltalk를 공부함에 있어서 이러한 세부적인 사항을 알 필요는 없기 때문에 여기서도 그냥 번호 붙은 성분이 이런 것이구나 정도만 알아두면 되겠습니다.


여기까지 오면서 필자가 걱정되는 것이 하나 있습니다. 바로 여러분이 Smalltalk에 대한 흥미를 조금씩 잃지나 않을까 하는 것입니다. 사실 아무리 좋은 것이라도 하루 이틀이 지나면 지겨워지기 마련입니다. 하기야 "200 factorial" 을 아무렇지도 않게 구해내는 Smalltalk 가 처음에는 매우 신기해 보이겠지만, 계속 이렇게 수만 다루다 보니 이제는 슬슬 흥미를 잃게 되는 것도 어쩌면 당연한 일일지도 모르겠습니다. 물론 어떤 프로그래밍 언어를 막론하고 기본적으로 수를 제대로 다룰 수 있어야만 하기 때문에 우리도 예외 없이 수를 많이 다루어왔습니다. 그러나 이제부터는 수 아닌 다른 객체들에 대해서 조금씩 알아보도록 하겠습니다.


앞서 우리는 어떤 객체에게 지시를 내렸을 때 "그렇다"(true)나 "아니다"(false)와 같이 참값이나 거짓값으로 된 결과를 얻은 적이 있습니다. 기억나십니까? 바로 아래의 글토막들은 모두 참이나 거짓의 값을 나타내게 됩니다.

10 isKindOf: Integer     true
0 between: 1 and: 10     false
2 = 2.0                  true


참을 나타내는 "true"나 거짓을 나타내는 "false" 역시 당당한 Smalltalk의 객체입니다. 따라서 이들 객체 또한 "inspect" 지시를 알아들을 수 있으며, 이는 곧 이들 객체들도 탐색기를 통해 속을 들여다볼 수 있음을 의미합니다. 이제 말이 난 김에 "true" 와 "false" 에 "inspect" 지시를 내려서 이들 객체의 속을 들여다보도록 합시다.

true inspect
false inspcet


위의 글토막을 가늠하면 다음 그림 2-9 와 같은 결과를 얻게 됩니다.

그림 2-9::탐색기로 들여다 본 true와 false (Stp2-9.gif)


그림 2-9 를 보면 "Inspecting a True" 와 "Inspecting a False" 라는 제목을 가진 두 개의 탐색기가 있습니다. 여기서 우리는 "true"는 "True" 갈래에 속하고, "false"는 "False" 갈래에 속한다는 것을 짐작할 수 있는데, 뭔가 좀 이상한 것 같습니다. 이것은 Smalltalk에서 "부울 값"(boolean)이라고도 불리는 논리값을 표현하기 위해서 사용하는 하나의 방법입니다. 이미 눈치를 채신 분이 있을지도 모르겠지만, Smalltalk에서는 대문자와 소문자를 구별합니다. 그래서 "false"와 "False"는 같은 철자로 이루어진 낱말이라 하더라도 대소문자를 구분하는 Smalltalk는 이를 서로 다른 의미를 가진 낱말로 알아듣습니다. Smalltalk에서 모든 갈래의 이름은 대문자로 시작하게끔 되어있기 때문에 "false"는 정확하게 "False"라는 이름을 가진 갈래에 속한 실체인 것입니다.


이렇게 장황하게 설명은 했지만, 실제로 "false"와 "true"의 속을 들여다 보면 별 것이 없습니다. 왜냐하면 "true"와 "false"는 성분을 가지고 있지 않은 홀객체이기 때문입니다. :)


이 두 개의 객체들에 대해서도 "class"와 "superclass" 지시를 사용하여 갈래씻줄(class hierarchy)을 더듬어봅시다.

true class               True
True superclass          Boolean
Boolean superclass       Object

false class              False
False superclass         Boolean
Boolean superclass       Object


"true"와 "false"는 모두 "Boolean" 갈래에 씻줄로 얽혀있다는 것을 알 수 있습니다. 뒤에서 살펴보겠지만 논리값(Boolean)은 어떤 사실의 판단을 통하여 얻을 수 있는 값이기 때문에, 이 논리값이 알아들을 수 있는 여러 가지 지시를 통하여 프로그램의 실행 흐름을 자유롭게 조절할 수 있게 됩니다. 일단 논리값이 알아들을 수 있는 지시에 대해서는 조금 있다가 생각해 보기로 히고, 여기서는 참값과 거짓값이 어떤 갈래씻줄로 얽혀있는지를 살펴보기 바랍니다.


마지막으로 알아볼 객체는 "헛"(nil)입니다. 다음과 같이 하면 "헛"을 결과값으로 얻을 수 있습니다.

Object superclass    nil


"nil"은 "아무 것도 없는, 비어 있는, 공허한"의 뜻을 가지고 있습니다. Smalltalk에서는 객체에게 어떤 지시를 내렸을 때 해당하는 값이 없거나 잘못되었다는 의미로 사용됩니다. 모든 갈래씻줄의 뿌리가 "Object" 갈래이기 때문에 결국 "Object" 갈래의 윗갈래는 있을 수 없습니다. 따라서 "Object" 갈래에게 "superclass" 지시를 내리면 당연히 결과값으로 "헛"(nil)을 남기게 되는 것입니다.


"nil"도 엄연한 객체이기 때문에 "inspect" 지시를 알아듣습니다.

nil inspect


위의 글토막을 실행시키면 그림 2-10 과 같이 "Inspecting a Undefind Object" 라는 제목을 가진 탐색기가 나타날 것입니다. "nil" 객체 역시 아무런 성분을 가지고 있지 않는 홀객체라는 것을 알 수 있습니다.


'그림 2-10::탐색기로 들여다 본 nil (Stp2-10.gif)


"nil" 역시 갈래씻줄을 가지고 있습니다. Smalltalk의 모든 것이 객체라는 것과, 객체는 각각의 갈래에 속해있으며, 그 갈래는 다른 갈래들과 씻줄로 얽혀있다는 것, 이제는 귀가 따감도록 들어 온 사실이기에, 잊어버리지 않을 것입니다. :)

nil class                        UndefinedObject
UndefinedObject superclass       Object


"nil"이 속해 있는 "UndefindObject"라는 갈래는 "알 수 없는 객체" 쯤으로 번역해볼 수 있습니다. "nil"이라는 객체의 성질을 참으로 잘 표현하고 있는 갈래라는 것을 알 수 있습니다. 여하튼 "nil" 역시 엄연한 객체이며, 이 객체 역시 "UndefinedObject"라는 갈래에 속해있다는 것을 알 수 있었습니다.


그런데 혹

Object superclass   nil


의 결과를 보고, "nil"에게 "superclass" 지시를 내릴 수 있을까 하는 의문을 갖는 사람들이 있을지도 모르겠습니다. "nil"은 갈래가 아니기 때문에 당연히 "superclass" 지시를 알아듣지 못합니다. 실제로 실험을 해 보면 "UndefinedObject does not understand #superclass" 라는 제목이 달린 발자취창이 나타남을 알 수 있을 것입니다.

nil superclass


여하튼 "nil"은 객체이기는 하지만 갈래는 아니기 때문에 갈래가 알아들을 수 있는 "superclass" 지시는 알아듣지 못합니다.


갈래 씻줄의 의미와 객체의 성분


이 나눔을 닫기 전에 한 가지 짚고 넘어갈 것이 있습니다. 그것은 갈래 씻줄과 겉으로 드러나는 실제 객체의 모양은 큰 상관이 없다는 것입니다. 조금 애매한 말처럼 들린다면 아래와 같이 따져서 생각해 봅시다.


다음 글토막을 펴 보십시오.

10 factorial class       SmallInteger
100 factorial class      LargeInteger


"10 factorial"의 결과값은 분명히 "SmallInteger" 갈래에 속한 실체이고, "100 factorial"의 결과값은 "LargeInteger"에 속한 실체일 것입니다. 물론 작은 정수와 큰 정수는 모두 같은 정수 무리이기 때문에

10 factorial isKindOf: Integer           true
100 factorial isKindOf: Integer          true


은 너무나도 당연한 결과입니다. 그러나

10 factorial inspect
100 factorial inspect


위의 글토막을 가늠하여 작은 정수와 큰 정수의 실체를 탐색해 보면 두 객체의 구조는 서로 다르다는 것을 알 수 있습니다.


그림 2-11::SmallInteger와 LargeInteger의 구조


작은 정수가 홀객체인데 반해서 큰 정수는 번호 붙은 성분을 여럿 가지고 있는 핫객체인 것입니다. 이 경우는 68 개의 번호 붙은 성분을 가지고 있지만 실제로 큰 정수의 경우에는 값의 크기에 따라 서로 다른 수의 번호 붙은 성분을 가지고 있습니다.


여하튼 의미론적으로 볼 때 작은 정수와 큰 정수는 모두 정수라는 갈래에 딸려 있지만, 그렇다고 해서 두 객체의 구조가 같다는 말은 아니라는 것을 알아두면 좋겠습니다. Smalltalk에서는 겉으로 드러나는 모습은 같지만 속은 전혀 다른 객체들이 여럿 존재합니다.


지금까지 우리는 객체를 이루는 성분에 대해서 알아보았습니다. 정수, 참, 거짓, 헛(nil)처럼 성분을 가지지 않는 홀객체가 있는가 하면, 분수처럼 이름 붙은(named) 성분으로 이루어진 객체가 있고, 또한 소수처럼 번호 붙은(indexed) 성분으로 이루어진 객체도 있다는 것을 알 수 있었습니다. 사실 앞으로 우리들이 다루는 대부분의 객체는 홀객체가 아닌, 성분을 가지고 있는 핫객체입니다. 성분이 있다는 것은 그만큼 객체가 복잡하고 또한 하는 일이 많음을 의미합니다. 바로 다음 나눔에서 알아볼 글자(character)와 글줄(string)부터는 수 갈래에 속하지 않은 또 다른 객체의 세계에 대해서 경험할 수 있게 될 것입니다.


글자와 글줄

컴퓨터(computer)는 그 이름에서도 알 수 있듯이 "계산을 하기 위해서" 만들어졌습니다. 처음 컴퓨터가 만들어질 때는 세계가 전운에 휩싸였던 때이고, 어떻게 하면 대포를 정확한 각도에서 발사하여 목표물을 맞출 수 있을지를 계산하기 위해서 바로 컴퓨터가 사용되었습니다. 지금은 멀티미디어 시대인 만큼 컴퓨터로 처리할 수 있는 자료의 범위가 대단히 많아졌습니다. 그러나 실제로 이러한 자료들은 컴퓨터 내부에서는 모두 수(數)로 바뀌어 처리됩니다. 그래서 이러한 수로 이루어진 자료들을 '디지털 자료'(digital data)라고 부릅니다. '디지털'은 수를 구성하는 낱낱의 자리를 의미하는 "digit"에서 나온 말이라는 것을 생각하면, 컴퓨터가 모든 자료를 수로 바꾸어 처리하는 것은 당연한 일이라고 하겠습니다.


프로그래밍 언어를 처음 공부할 때에 수를 매우 중요하게 생각하고, 수를 다루는 방법을 많이 다루는 것도, 결국은 컴퓨터의 모든 자료들이 기본적으로 수로 처리되기 때문입니다. 이는 Smalltalk 에서도 마찬가지입니다. Smalltalk가 여러 가지 객체를 다루기는 하지만, 실제로 컴퓨터는 이들을 모두 수로 바꾸어 처리합니다. 앞서 정수, 소수, 분수와 같은 여러 종류의 수들을 다룬 것도, 기본적으로 이런 수(Number) 갈래에 속한 객체들이 가장 다루기 쉽고 기본적인 지시들을 알아들을 수 있기 때문입니다. 이제 여러분은 Smalltalk에서 객체를 다루는 여러 가지 방법을 공부해 보았으므로, 이제부터는 수가 아닌 다른 종류의 객체들을 다루어보도록 합시다.


컴퓨터에서 수 다음으로 중요한 자료를 꼽으라면 바로 글자와 글줄을 들 수 있겠습니다. 지금 필자가 글을 쓰기 위해서 사용하고 있는 문서 편집기 역시 글자를 다루고 있으며, 여러분이 통신상에서 필자의 글을 읽을 수 있는 것도 바로 컴퓨터가 글자를 다룰 수 있기 때문입니다. 이 나눔에서 여러분들은 Smalltalk 에서는 이러한 글자와 글줄을 어떻게 나타내고, 또한 이들 객체를 이용해서 어떤 일을 할 수 있는지를 알아보도록 하겠습니다.


Smalltalk에서 한 개의 글자는 다음과 같이 나타냅니다. 아래 글토막을 펴 보십시오.

$a   $a
$b   $b
$A   $A
$!   $!
$1   $1
$2   $2


보는 바와 같이, Smalltalk에서 글자 한 개를 나타내려면 "$"와 함께 표시하고싶은 글자를 적어주면 됩니다. $A, $B, $a, $b 의 경우는 영문자가이고, $1 이나 $2 의 경우는 숫자이며, $! 의 경우는 '느낌표'를 나타냅니다.


그럼 Smalltalk는 ㄱ, ㄴ, ㄷ 과 같은 한글의 낱글자를 어떻게 알아들을까요? 다음 글토막을 펴 봅시다.

$ㄱ             "Invalid expression start 잘못 발생"


위의 글토막을 펴게 되면 "Invalid expression start"라는 잘못을 내게 됩니다. 이 말은 결국 한글의 낱글자는 글자로써 다룰 수 없음을 의미합니다. 만약 Smalltalk가 우리 나라에서 만들어졌다면 한글도 하나의 글자로 처리할 수 있겠지만, 불행하게도 Smalltalk를 비롯한 컴퓨터의 여러 가지 산물은 영어권 문화에서 만들어졌기 때문에 한글에 대한 처리는 상대적으로 미약한 편입니다. 물론 바로 다음에 이야기할 글줄을 이용하여 한글을 나타낼 수는 있습니다만, 이에 관해서는 조금 뒤에 알아보도록 하겠습니다.


그럼 이제 이 글자는 어떤 갈래에 속해 있으며, 어떤 씻줄로 얽혀있는지를 살펴봅시다. 글자 역시 엄연한 객체라는 사실을 잊지 않도록 합시다.

$a class         Character
$b class         Character
$A class         Character
$B class         Character
$! class         Character
$1 class         Character
$2 class         Character

Character superclass     Magnitude
Magnitude superclass     Object


영문자나 숫자, 또는 느낌표와 같은 여러 가지 기호들은 모두 "Character" 갈래의 실체(instance)입니다. 따라서 이들 글자에게 "class" 지시를 보내면 결과로써 "Character" 갈래를 남기게 됩니다.


이렇게 해서 남겨진 "Character" 갈래에 "superclass" 길표를 이용해서 씻줄을 더듬어 올라갈 수 있습니다. 결국 "Character"(글자)는 "Magnitude"(크기값)이고, 동시에 "Object"(객체)입니다.


모든 객체는 "inspect" 지시를 알아들을 수 있기 때문에, 글자 역시 똑같이 "inspect" 지시를 알아들을 수 있습니다. $a, $A, $! 세 개의 글자를 탐색해 봅시다.

$a inspect
$A inspect
$! inspect


위의 세 개의 글토막을 하나씩 가늠하거나 $a, $A, $! 객체를 각각 Ctrl-I 글쇠를 이용하여 탐색하게 되면 다음 그림 2-12 와 같이 세 개의 탐색기가 열린 화면을 볼 수 있을 것입니다.

그림 2-12::$a, $A, $! 를 탐색한 모습 (Stp2-12.gif)


탐색기에서 볼 수 있듯이 "Character" 갈래에 속한 객체들은 모두 "ascii Value" 라고 하는 성분을 가지고 있는 핫객체입니다. 이 "asciiValue" 성분에는 어떤 값이 들어있을까요? $a 객체의 "asciiValue"를 탐색해 봅시다. $a를 표시하고 있는 탐색기의 왼쪽 널(pane)에서 "asciiValue" 선택가지를 돋이시키면 다음 그림과 같이 "asciiValue"의 값을 볼 수 있을 것입니다.

그림 2-13::$a 객체의 성분 "asciiValue"의 탐색 (Stp2-13.gif)


Dolphin Smalltalk의 심각한 문제


그런데 여기서 필자를 당황하게 하는 일이 일어났습니다. 바로 탐색기의 오른쪽 널에 무언가 심상치 않은 값이 나타났기 때문입니다. 다음과 같은 글귀가 나타났는데, 과연 이것이 "asciiValue"의 값일까요?


an invalid UndefinedObject [should not implement]

Evaluate the following expression to debug:
        asciiValue printString


위의 말을 풀이해 보면 탐색기가 "asciiValue"의 값을 표시하는 데에 무언가 문제가 있으며, 문제를 해결하기 위해서는 "asciiValue printString"이라는 글토막을 가늠해 보라는 것입니다.


필자가 처음 이 상황을 만났을 때 매우 당황했습니다. 그 당시는 모든 것이 낯설었던 Smalltalk를 처음 배우는 시기였으므로, 도대체 뭐가 어떻게 된 건지를 알 수 없었습니다. 한 술 더 떠서 이 상태에서 "asciiValue"를 두 번 눌러서 연계 탐색 기능을 동작시키면 발자취 창이 표시되었으니, 황당했던 것은 이루 말할 수 없었습니다. 그러나 침착하게 일단 Smalltalk 가 하라는 대로 다음의 글토막을 가늠해 보았습니다.

asciiValue printString   '97'


위의 글토막을 펴 본 화면이 다음의 그림 2-14 입니다.

그림 2-14::Smalltalk가 제시한 글토막으로 얻은 asciiValue의 값 (Stp2-14.gif)


비로소 필자는 $a 객체의 성분인 "asciiValue"의 값을 얻을 수 있었습니다. $a 객체의 "asciiValue"의 값은 97이었던 것입니다.


원래 "asciiValue" 성분은 정수값을 가지고 있어야 합니다. 따라서 제대로 되었다면 오른쪽 널에 정수값 97이 표시되어야 옳습니다. 그러나 어찌된 일인지 Dolphin Smalltalk는 잘못된 결과를 출력했으며, 사용자에게 직접 이 성분의 값을 출력하도록 하고 있습니다.


인터넷의 뉴스그룹에 이 사실을 통보했었습니다. 그래서 얻은 답은 바로 이것이었습니다. 현재 Dolphin Smalltalk는 한글과 같이 여러 나라의 글자들을 처리하기 위해서 제정된 UniCode라는 것을 구현하고 있는 중이랍니다. 일단 급한 대로 UniCode가 지원되지 않은 Smalltalk를 내놓았는데, 글자를 다루는 일부 기능에서 문제가 생겼다는 것이 Object Arts사의 주장입니다. 결국 "Character" 갈래에 속한 객체의 "asciiValue" 값을 제대로 표시하지 못하는 것도 이러한 이유 때문이라고 합니다. 어쨌건 좀 불편하더라도 "Character" 갈래에 속한 객체의 "asciiValue" 값을 탐색할 때에는 조금 불편하더라도 Smalltalk가 제시해 주는 글토막을 가늠하는 수밖에는 없을 것입니다. 하루 빨리 이 문제가 해결되기를 바랄 뿐이지요.


필자가 실험해 본 결과 Smalltalk Express의 경우는 제대로 결과를 나타내는 것으로 확인되었습니다.


$a의 "asciiValue" 값은 97이었습니다. 그럼 $A의 경우는 얼마일까요? 역시 앞서와 같은 방법으로 $A 객체의 "asciiValue" 성분의 값을 알아보면, 65라는 것을 알 수 있습니다. 마찬가지로 $! 의 경우는 33이라는 것을 알 수 있습니다. 자, 과연 이 "asciiValue"는 무슨 의미를 가지고 있는 성분일까요?


앞에서 컴퓨터는 모든 자료를 수로 바꾸어서 처리한다고 이야기했습니다. 정수는 물론이고 소수나 분수 모두 결국은 수로 이루어진 객체들이었습니다. 이는 글자도 예외가 아니어서, 컴퓨터가 글자를 처리하기 위해서는 역시 글자도 수로 다루어야 했고, 결국 글자에 번호를 붙여서 나타내게 되었습니다.


그런데 글자에 번호를 붙이는 방법이 한 가지만 있는 것이 아니라는 것입니다. 컴퓨터가 처음 만들어졌을 때만 하더라도, 컴퓨터를 만들어서 팔던 회사들은 저마다 나름대로 글자에 번호를 붙였습니다. 이렇게 컴퓨터마다 글자에 번호를 붙이는 약속이 달랐기 때문에, 가령 "가"라고 하는 컴퓨터에서 다루었던 글자를 "나"라는 컴퓨터는 전혀 다른 글자로 알아듣게 된 것입니다. 이렇게 되면 정보의 호환성이 떨어지게 되고, 이런 폐단을 없애기 위해서 여러 사람이 똑같은 약속으로 글자에 번호를 붙이게 되었습니다.


마침내 미 국립 표준 위원회(ANSI: American National Standard Institute)에서는 ASCII(American Standard Code for Information Interchange, 정보 교환용 미국 표준 코드)를 제정하게 됩니다. 따라서 ASCII라는 약속을 따르는 컴퓨터에서는 글자에 번호를 붙이는 방식이 모두 똑같이 되었습니다.


"Character" 갈래에 속한 모든 글자의 실체(instance)들이 가지고 있는 성분인 "asciiValue"는 바로 위에서 말한 글자의 ASCII 값이 들어 있습니다. 따라서 글자 $a의 ASCII 값은 97, $A의 경우는 65이며, $!의 경우는 33이 되는 것입니다.


모든 글자에 ASCII 값이 부여되어 있으므로, 탐색기를 통해서 각 글자에 매겨진 ASCII 값을 정리하여 표로 만들어볼 수 있을 것입니다. 그러나 모든 글자에 대한 번호표를 일일이 만드는 것은 지루한 일이므로, 여러분의 수고를 필자가 덜어드리겠습니다. 아래 표는 글자와 ASCII 값의 대응을 정리한 것입 니다.

글자 ASCII 값
$a ... $z 97 ... 122
$A ... $Z 65 ... 90
$0 ... $9 48 ... 57
$( 40
$) 41
$$ 36
$. 46
$, 44
$<빈칸> 32
$<줄바꿈> 13


위의 번호료를 보면 몇 가지 재미있는 사실을 발견할 수 있습니다. 우선, 대문자에 붙여진 번호가 소문자의 글자 번호보다 크다는 것입니다. 즉 대문자 $A의 경우는 65, 소문자 $a의 경우는 97이라는 것이 이를 증명해 줍니다. 더욱이 대문자와 소문자 사이에는 정확하게 32 라는 차이가 있습니다. 97에서 65를 빼 보면 32라는 값이 나옴을 알 수 있는 것이지요. 그래서 만약 대문자 $A의 값인 65에 32를 더하면 소문자 $a의 값인 97이 됩니다.


숫자의 경우도 생각해 봅시다. 수는 기본적으로 영문자보다 더 낮은 번호를 가지고 있습니다. 그리고 이러한 숫자들과 실제의 값에는 아무런 관계가 없다는 것을 알 수 있습니다. $0의 경우 컴퓨터는 이를 "0"이라는 값으로 처리하는 것이 아니라 "48" 이라는 값으로 처리합니다. 그러므로 숫자와 그 숫자가 의미하는 값은 서로 차이가 있다는 것을 알아야 합니다.


또한 모든 글자에는 ASCII 값이 부여되어 있습니다. $$, $., $, 등과 같은 모든 글자에는 나름으로의 번호가 붙어 있으며, 심지어는 눈에 보이지 않는 빈칸과 줄을 바꿀 때 사용하는 줄바꿈 글자 역시 번호를 가지고 있습니다.


글자가 컴퓨터 내부에서 다루어질 때에는 이렇게 번호가 붙은 값으로써 처리됩니다. 따라서 글자 객체에게 그 자신을 나타내는 ASCII 값이 몇인지를 물어볼 수 있습니다. 이는 글자 객체에게 "asciiValue"라는 지시를 보냄으로써 가능합니다. 다음의 글토막을 펴 봅시다.

$a asciiValue    97
$A asciiValue    65
$0 asciiValue    48
$( asciiValue    40


앞서 빈칸과 줄바꿈 글자 역시 번호를 가지고 있다고 했으므로, 다음과 같이 실험을 해 봅시다.

$ asciiValue   32


$ 다음에 빈칸이 있습니다. 우리 눈에는 이 빈칸이 보이지 않지만 컴퓨터는 이를 엄연히 하나의 글자로 처리합니다. 이는 줄바꿈 글자도 마찬가지입니다. 다음의 글토막을 펴 봅시다. 글토막이 두 줄이므로 덩이를 씌운 다음 펴야 한다는 것쯤은 이제는 아시겠지요?

$ 
asciiValue   13


글쇠판에서 $ 를 누른 다음 바로 -Enter 글쇠를 눌러서 줄을 바꾸고 이어서 "asciiValue"를 입력한 다음 방금 입력한 두 줄에 덩이를 씌웁니다. 마지막으로 글쇠판에서 Ctrl-D 를 입력하면 위와 같이 글토막을 가늠할 수 있습니다. 필자가 정리한 대로 빈칸의 경우는 32가, 줄바꿈의 경우는 13 이 결과값으로 나타났습니다.


글자에게 "asciiValue" 지시를 보냄으로써 해당하는 ASCII 값을 얻을 수 있었다면, ASCII 값을 해당하는 글자로 바꾸어줄 수 있는 방법은 없을까요? 물론 있습니다. 똑똑한 정수는 "asCharacter"라는 지시를 알아듣고, 지시를 받은 값에 해당하는 글자를 결과값으로 남깁니다. 다음 글토막을 펴봅시다.

97 asCharacter   $a
65 asCharacter   $A
48 asCharacter   $0
42 asCharacter   $*
43 asCharacter   $+


따라서 필요하면 수를 글자로 다룰 수도 있으며, 글자에 해당하는 수로 바꾸어서 다룰 수도 있습니다. 이런 특성을 이용하면 다음과 같은 글토막을 만들 수 있습니다.

($a asciiValue + 1) asCharacter  $b


위의 글토막은 $A라는 글자의 바로 다음 글자를 얻는 바탕글입니다. 우선 $A에 "asciiValue" 지시를 보내서 수로 바꿉니다. 이렇게 바뀌어진 수는 계산에 사용할 수 있으므로, 이 수에 1을 더합니다. 그런 다음 이렇게 해서 계산된 수에 "asCharacter" 지시를 보내면, 결국 $A의 다음 글자인 $B를 얻을 수 있는 것입니다. 길표의 우선권 규칙을 제대로 따져서 생각하면 위의 글토막이 왜 $B를 결과값으로 남겼는지를 따져서 생각할 수 있을 것입니다.


"Character" 갈래에 속한 객체들은 "asciiValue"와 더불어 여러 가지 많은 지시를 알아들을 수 있습니다. 주로 해당 글자가 어떤 종류의 것인지를 검사하거나, 영문자의 경우 대소문자의 변환 등을 처리하는 역할을 하고 있습니다. 이제부터는 글자 객체아 알아들을 수 있는 몇 가지 지시들을 알아보도록 하겠습니다.

$A isUppercase   true
$A isLowercase   false

$a isUppercase   false
$a isLowercase   true


"isUppercase", "isLowercase"는 "대문자인가", "소문자인가"로 읽을 수 있습니다. 즉 이 지시를 받은 글자가 대문자인지 혹은 소문자인지를 검사해서 참값(true)이나 거짓값(false)을 결과값으로 돌려줍니다.


만약 영문자가 아닌 숫자가 기호에 "isUppercase"나 "isLowercase" 지시를 내리면 어떤 반응을 보일까요?

$+ isUppercase   false
$+ isLowercase   false


두 경우 모두 거짓값을 남겼습니다. 영문자가 아니라는 말은 결국 대문자도 아니고 그렇다고 소문자도 아니라는 말입니다. 그러므로 영문자가 아닌 글자들이 "isUppercase"나 "isLowercase"에 대해서 거짓값을 남기는 것은 당연한 것이겠지요.


영문자에게 "asUppercase"와 "asLowercase"라는 지시를 보냄으로써 대문자는 소문자로, 소문자는 대문자로 바꿀 수 있습니다. 다음을 보십시오.

$a asUppercase   $A
$A asUppercase   $A
$+ asUppercase   $+

$A asLowercase   $a
$A asLowercase   $a
$+ asLowercase   $+


대문자에게 "asLowercase" 지시를 보내면 결과값으로 소문자를 남기고, 소문자에게 "asUppercase" 지시를 보내면 결과값으로써 대문자를 남깁니다. 그리고 대문자에게 "asUppercase"지시를 보내거나, 소문자에게 "asLowercase" 지시를 보내거나, 영문자가 아닌 글자에 이들 지시를 보내면 결과값은 변화가 없습니다. 이처럼 "asUppercase", "asLowercase" 지시는 영문에 대해서 대문자와 소문자의 변환을 수행하는 중요한 길표입니다.


이제 마지막으로 세 개의 길표를 더 알아보고자 합니다. 아래의 글토막들을 차례로 가늠해 보고 각 지시가 어떤 일을 하는지를 눈여겨 보시기 바랍니다.

$a isLetter      true
$A isLetter      true
$+ isLetter      false
$0 isLetter      false


"isLetter"는 지시를 받은 글자가 영문자이면 참을, 그렇지 않으면 거짓을 남깁니다.

$a isDigit       false
$A isDigit       false
$+ isDigit       false
$0 isDigit       true


"isDigit"는 지시를 받은 글자가 숫자이면 참을, 그렇지 않으면 거짓을 남깁니다.

$a isVowel       true
$A isVowel       true
$x isVowel       false
$X isVowel       false


"isVowel"은 지시를 받은 글자가 영어의 모음(a, e, i, o, u, A, E, I, O, U)이면 참을, 그렇지 않으면 거짓을 남깁니다.


Smalltalk에서 "is~"로 시작하는 길표는 객체의 어떤 상태를 조사하는 일을 합니다. 해당하는 상태가 맞으면 참을, 그렇지 않으면 거짓을 남깁니다. 또한 "as~"로 시작되는 지시는 한 객체를 다른 객체로 바꾸는 일을 합니다. 이를태면 정수 객체에게 "asCharacter" 지시를 보낸다는 것은, "Character" 갈래에 속한 객체로 바꾼다는 말이며, "asLowercase"는 대문자로 바꾼다는 의미를 가지고 있습니다.


지금까지 우리는 글자 객체에 대해서 여러 가지 지시를 내려보았습니다. Smalltalk에서의 글자는 매우 간단한 객체이기는 하지만, 바로 다음에 살펴볼 글줄을 이루는 매우 중요한 요소입니다. 이제 글자에 대해서 여러 가지를 알아보았으므로, 다음에는 글줄에 대해서 알아보도록 하겠습니다.


Smalltalk의 이식성


흔히 C나 C++ 언어의 특징을 설명할 때 "이식성이 좋다"는 말을 많이 합니다. 이식성이 좋다는 말은 한 시스템에서 작성된 프로그램을 다른 시스템으로 옮기기가 쉽다는 말인데, Smalltalk 역시 이러한 이식성이 매우 좋은 언어에 속합니다.


실제로 Smalltalk 시스템에 따라서 글자를 나타내거나 구현하는 방법이 조금씩 다릅니다. 일례로 Dolphin Smalltalk의 경우에는 "Character"에서 ASCII 값을 저장하기 위해서 "asciiValue"라는 성분을 사용하고 있지만, Smalltalk Express의 경우에는 "asciiInteger"라는 성분을 사용합니다. 그러나 글자의 ASCII값을 알아보기 위해서는 똑같이 "asciiValue"라는 지시를 사용합니다. 즉 객체의 내부가 어떻게 이루어져있는지는 프로그래머에게 문제가 되지 않습니다. 단지 그 객체를 다루는 방법만 알고 있으면 되는 것입니다. Smalltalk의 이러한 특성은 한 시스템에서 만든 프로그램을 다른 시스템으로 옮기기 쉽도록 합니다. 양쪽 시스템에 동일한 객체가 존재하면, 그 객체의 성분이 어떻든 서로 똑같은 프로그램을 돌릴 수 있습니다.


즉 "asciiValue"를 성분으로 갖는 Dolphin Smalltalk 의 경우나 "ascii Integer"를 성분으로 갖는 Smalltalk Express의 경우 모두 "asciiValue"라는 지시는 변함이 없으므로, 두 Smalltalk 시스템에서 똑같이

$a asciiValue


라는 지시를 이용할 수 있습니다. 따라서 이와 같은 특성은 C 나 C++ 보다 Smalltalk가 보다 더 이식성을 좋게 만드는 특징이라 하겠습니다.


Smalltalk에서 글자 못지 않게 중요한 자료로써 글줄(string)이 있습니다. 글줄은 하나 이상의 글자로 이루어져 있으며, 지금 필자가 쓰고 있는 것과 같은 글을 다루는 매우 중요한 자료입니다. 이제 여기서는 이러한 글줄에 대해서 알아보도록 하겠습니다.


우선 Smalltalk에서 글줄을 나타내는 방법을 알아보겠습니다.

'Hello'          'Hello'
'ghostly'        'ghostly'
'우리 나라'     ☞ '우리 나라'


위에서 예로 든 'Hello, 'ghostly', '우리 나라' 는 모두 글줄(string)입니다. 위에서 볼 수 있듯이 Smalltalk에서는 작은따옴표(' ') 안에 들어가 있는 모든 것을 글줄로 처리합니다.


글줄 역시 객체이기 때문에 "inspect" 지시를 알아듣습니다. 위의 세 개의 글줄에 "inspect" 지시를 보내거나, 또는 위의 글줄을 글토막으로 생각하고 Ctrl-I 글쇠를 이용하여 탐색해 봅시다.

그림 2-15::'Hello', 'ghostly', '우리 나라'의 탐색 (Stp2-15.gif)


글줄은 번호 붙은(indexed) 성분을 가지고 있는 핫객체입니다. 그런데 앞서 살펴본 소수(Float) 와는 달리 글줄(string)의 경우는 성분의 개수가 저마다 다 다릅니다. 앞서 탐색한 글줄들의 성분은 각각 몇 개였습니까?


'Hello'의 경우는 5개, 'ghostly'는 7개, '우리 나라'의 경우는 9개의 성분들을 각각 가지고 있습니다. 그럼 이러한 성분들에는 무엇이 들어있을까요?


이미 짐작한 사람들도 있을지 모르겠습니다만, 글줄의 성분에는 바로 글자들이 들어있습니다. 앞서 글줄은 글자의 모임이라는 말을 한 적이 있습니다. 그럼 실제로 탐색기에서 각각의 글자들이 어떤 성분으로 되어있는지를 알아봅시다.


'Hello'의 경우에는...

성분 글자
1 $H
2 $e
3 $l
4 $l
5 $o


와 같이 되어 있고, 'ghostly'의 경우는

성분 글자
1 $g
2 $h
3 $o
4 $s
5 $t
6 $l
7 $y


처럼 되어있습니다. 즉 글자를 이루는 각각의 번호 붙은 성분에는 해당 글줄을 구성하는 글자들이 들어있습니다.


그런데 여기서 의문을 가지는 사람들이 있을지 모르겠습니다. 그것은 바로 글줄을 구성하는 성분 중에 왜 작은따옴표(' ')가 없느냐는 것입니다. 우리는 실제로 글줄을 나타낼 때 'Hello'처럼 나타내었고, 따라서 글줄에 당연히 작은따옴표가 들어가야 한다고 생각하는 사람들이 있을 수 있습니다. 그러나 여기서의 따옴표는 단순히 글줄의 시작과 끝을 나타내는 기호일 뿐입니다. 그러므로 실제로 글줄의 내용이 아니라는 것을 기억해 두시기 바랍니다. 이는 마치 "A"라는 글자를 나타내게 위해서 $A라고 쓰는데, 여기서 "$"는 글자의 성분이 아닌 것과 같은 이치라고 생각하면 되겠습니다.


그렇다면 '우리 나라'의 경우는 어떨까요? 실제로 탐색기를 통해 확인해 보면, 엉뚱하게도 각각의 성분에 들어있는 글자를 제대로 표시하지 못한다는 것을 알 수 있습니다. 이는 앞에서 한글은 두 개의 Smalltalk 글자로 이루어져 있다고 말한 것과 깊은 관계가 있습니다. 즉 Smalltalk 처럼 영어권에서 개발된 프로그래밍 언어의 경우 하나의 글자로는 한글을 나타낼 수 없음을 가리키는 좋은 보기가 되는 것입니다.


그렇다고 글줄에 한글을 담아서 나타낼 수 없는 것은 아닙니다. 옛날에 만들어진 몇몇 프로그램의 경우는 한글 자체를 처리할 수 없는 경우가 많았는데, 다행이 우리의 Dolphin Smalltalk 는 한글을 어렵지 않게 처리할 수 있습니다. 그러므로 한글을 다룰 때에는 항상 두 개의 Smalltalk 글자를 한 묶음으로 해야 한다는 것만 기억해 두도록 합시다.


Smalltalk에서의 한글


Smalltalk 에서도 한글은 한 바이트(byte)에 처리되지 못하기 때문에 하나의 글자로 처리될 수 없는 어려움을 그대로 가지고 있습니다. 이것은 한글의 낱글자로 조합하여 나타낼 수 있는 글자의 수가 한 바이트로는 도저히 감당하기 어렵기 때문에 생긴 일입니다. 따라서 현재는 한글로 이루어진 글줄을 낱글자로 탐색하는 방법은 없다고 보는 것이 옳습니다. 다만 다음의 글토막을 이용하면 글줄을 구성하고 있는 글자들의 ASCII코드를 16진수로 보여줄 수는 있습니다. 아래의 <바탕글 1>에 모두 덩이를 씌워서 Ctrl-D 글쇠로 펴보면 결과를 얻을 수 있습니다.

<바탕글 1> 글줄을 이루는 바이트의 코드 확인하기

| result |
result := String new.

'우리 나라' do: [ :ch |
        result := result , 
                (ch asciiValue printStringRadix: 16 showRadix: false).
        result := result , ' '.
].
result.


[실행 결과]
☞ 'BF EC B8 AE 20 B3 AA B6 F3 '


여하튼 제대로 한글 처리가 이루어지기 위해서는 UniCode 를 지원하는 Smalltalk를 사용하는 수밖에는 없는데, 앞서도 말했듯이 Dolphin 에서는 현재 UniCOde를 지원하지 않으므로 당분간은 기존 프로그래밍 언어에서 겪었던 불편함을 그대로 치러야 할 것 같습니다.


사족을 달자면, 한글이 한 바이트로 표현되지 못하기 때문에 후진 글자가 절대로 아닙니다. 오히려 낱글자를 가지고 만들 수 있는 글자의 수가 256 가지를 넘는 문자가 있다는 것을 간파하지 못한 컴퓨터 개발자들의 탓일 것입니다. 이는 단지 기억공간의 낭비를 막기 위해 세기(世紀)를 표기하지 않아서 생기는 "Millenium bug"와도 같은 것입니다. 먼 앞을 내다보지 못해서 허우적대는 것이 어디 "Y2k" 뿐이겠습니까? 우리가 늘 사용하는 컴퓨터이지만, 한글을 제대로 표현할 수 없는 이 문제는, 정말 "Y2k" 만큼이나 중대한 문제라는 것을, 사람들은 제대로 느끼지 못하는 모양입니다.


글줄(string) 역시 탐색기에서 값을 바꿀 수 있는 객체입니다. 'ghostly'를 탐색하고 있는 탐색기를 이용하여 글줄을 한 번 바꾸어보도록 합시다. 만약 앞서 'ghostly'를 탐색하고 있는 탐색기를 닫았다면 일터에 'ghostly' 글줄을 입력하고 덩이를 씌운 후 Ctrl-I 글쇠로 탐색해서 탐색기를 여십시오.


이제 우리는 6, 7번 성분을 $l, $y에서 $', $s로 바꿀 것입니다. 다음 절차를 따르면 됩니다.

  1. 선택 막대를 "6"으로 옮겨서 6번 성분이 표시되게 한다.
  2. 오른쪽 널에 $l 이 표시되었는지 확인한다.
  3. $l에서 'l'을 지우고 작은따옴표(')로 바꾼다.
  4. 오른쪽 널의 변경을 적용하기 위하여 Ctrl-S 를 누르거나 Workspace > Accept 메뉴를 실행한다.
  5. 마찬가지로 선택 막대를 "7"로 옮긴다.
  6. 오른쪽 널에 표시된 $y를 $s 로 바꾼다.
  7. 변경을 적용하기 위해서 Ctrl-S 글쇠를 누르거나 Workspace > Accpet 메뉴를 실행한다.


이제 우리가 변경한 것을 확인해 보기 위해서 왼쪽 널의 "self"를 돋이시켜 봅시다. 그러면 다음 그림과 같은 결과를 볼 수 있을 것입니다.

그림 2-16::'ghosts' 글줄을 탐색하는 화면 (Stp2-16.gif)


어떻게 되었습니까? 화면에 'ghosts' 라고 나타났습니다. 우리는 분명히 작은따옴표를 하나만 넣었는데 화면에는 두 개의 작은따옴표가 있는 것으로 나왔습니다.


여기서 우리는 한 가지 중요한 사실을 알 수 있습니다. 바로 글줄 안에 작은따옴표를 넣기 위해서는 두 개의 작은따옴표가 필요하다는 것입니다. 만약 'ghost's'처럼 나타낸다면 Smalltalk는 $t의 다음에서 글줄이 끝난다고 생각하고, $s의 다음에서 새로운 글줄이 시작된다고 판단하게 됩니다. 따라서 컴퓨터가 글줄 속에 들어있는 따옴표를 제대로 판단하게 하기 위해서, 글줄 속에 있는 두 개의 잇따른 작은따옴표를 하나의 작은따옴표로 처리하도록 한 것입니다.


여기서 "잇따른"이라는 말이 매우 중요합니다. 만약 'ghost' 's'처럼 두 개의 작은따옴표 사이에 빈칸이 들어가면 이는 하나의 글줄이 아니라 두 개의 글줄이기 때문입니다. 그러므로 Smalltalk에서 글줄과 함께 작은따옴표를 다룰 때에는 특히 주의가 필요합니다.


작은따옴표의 사용법을 어느 정도 익혔다면, 이제 아래의 글토막들을 탐색해 보고 결과를 살펴봅시다. 여러분이 생각한 결과와 같다면 작은따옴표의 사용법에 대해서 완전히 익숙해진 것입니다.

'God''s car'
'It''s a boy!'
'''hi'''


글줄에서 잇따른 작은따옴표는 하나의 작은따옴표(')로 취급한다는 것을 바로 앞서 알아본 바 있습니다. 그렇다면 다음 글토막을 가늠하면 어떤 결과를 얻을 수 있을까요?

''  ''


편 글줄이 그대로 나타났습니다. 그렇다면 이제는 위의 글줄을 펴지 말고 탐색해 봅시다. 그러면 다음 그림과 같은 결과를 보게 될 것입니다.

그림 2-17::빈 글줄()의 탐색 (Stp2-17.gif)


탐색기의 제목을 보십시오. "Inspecting a String"이라고 되어있습니다. 그러므로 우리는 지금 분명히 글줄(string)을 탐색하고 있는 것입니다. 그런데 열린 탐색기의 왼쪽 널에는 "self" 만 덩그러니 나와 있습니다. 아무런 성분도 없습니다. 이것은 이 아무런 글자도 들어있지 않은 "빈 글줄"(empty string; null string) 이기 때문입니다.


정리하면, 글줄 속에 따옴표가 잇따라 두 번 나오면 하나의 작은따옴표로 알아듣지만, 단순히 작은따옴표 두 개가 잇따라 나오면 그것은 아무런 글자도 들어있지 않는 빈 글줄을 나타냅니다. 글자가 아무 것도 없으니 탐색을 해 봐도 아무런 성분도 보이지 않는 것은 너무나 당연하겠지요?


그렇다면 작은따옴표 하나만 들어있는 글줄을 나타내려면 어떻게 하면 될까요? 이것은 여러분의 몫으로 남겨둡니다. 객체와 언제든지 대화하고 여러 가지 결과를 직접 시험할 수 있는 Smalltalk의 특징을 살려서 여러분이 일터에서 직접 글토막을 입력해서 펴보거나 탐색해 보면 쉽게 문제를 풀 수 있을 것입니다. :)


이제 글줄의 특성을 어느 정도 파악했으니, 남은 것은 글줄의 갈래를 알아보고 어떤 씻줄로 얽혀있는지를 조사해 볼 차례입니다 :). 언제나 그렇듯이 Smalltalk에서는 모든 것이 객체이므로, 객체의 성질을 제대로 파악하기 위해서는 먼저 그 객체가 어떤 갈래에 속해 있으며, 어떻게 씻줄에 얽혀있는지를 알아야만 합니다. 우리가 늘 하던 대로 실체(instance)에 "class" 지시를 내려서 일단 글줄의 갈래를 얻고, 얻어진 갈래에 "superclass" 지시를 내려서 씻줄을 더듬어 올라가 봅시다.

'우리 나라' class                String
String superclass                ArrayedCollection
ArrayedCollection superclass     SequenceableCollection
SequenceableCollection superclass        Collection
Collection superclass            Object


글줄의 씻줄을 따라 올라가다 보면 꽤나 이상한 갈래들을 만날 수 있습니다. 여태껏 우리가 접해보지 못한 이상 야릇(?)한 갈래들. 저런 것들은 뭘까? 영어로 되어있어서 어려울 뿐입니다. 쉽게 우리말로 풀어쓰면 아래와 같이 쓸 수 있습니다.

String                  글줄
ArrayedCollection       배열 모듬
SequenceableCollection  나란한 모듬
Collection              모듬


"모듬"(collection)이란 하나 이상의 객체들의 모임을 말하고, 그 아랫갈래인 "나란한"(sequenceable) 모듬은 객체들이 나란히 늘어서 있는 모듬이며, "배열"(arrayed) 모듬은 객체들이 줄지어 있는 모듬을 의미합니다. 물론 각 모듬들의 특성이 매우 다르고 또한 나름대로의 쓰임이 있기 때문에, 이런 모듬들에 대해서는 나중에 알아보도록 하겠습니다. 여기서는 글줄이 여러 글자들이 줄줄이, 그리고 나란히 늘어서 있는 모듬이라는 정도로만 이해하면 될 것입니다.


이제 글줄의 갈래와 씻줄도 어느 정도 파악했으니, 이러한 글줄이 알아들을 수 있는 몇 가지 지시들을 살펴보도록 하겠습니다. 글줄로 할 수 있는 일을 생각해 보면 어떤 것들이 있을까요? 두 개의 글줄을 더해서 하나의 글줄로 만들 수도 있을 것이고, 글줄의 길이도 알아낼 수 있으면 좋겠군요. 이러한 일을 하려면 글줄에게 어떤 지시를 내려야 할까요?


다음 글토막들을 펴 보십시오.

'yes', 'ter', 'day'      'yesterday'
'우리', '나라'          ☞ '우리나라'


여기서 쉼표(,)는 말과 말 사이를 갈라주는 가름표(delimiter)가 아니라 어엿한 하나의 겹마다 길표(binary selector)입니다.


위의 예에서도 볼 수 있듯이 글줄이 "," 지시를 받으면 인자로 전달된 글줄과 자신을 합쳐 하나의 글줄을 만듭니다. 즉 "," 길표는 두 개의 글줄을 더하는 역할을 합니다. ","를 연달아 쓸 수 있는 까닭은, 우선권이 같은 길표의 경우 언제나 왼쪽에서부터 오른쪽으로 가늠하기 때문입니다.


다음으로는 "at:"이라는 길표에 대해서 알아봅시다. 다음을 펴 보십시오.

'yes' at: 1      $y
'yes' at: 2      $e
'yes' at: 3      $s


길표 "at:"은 한 개의 인자(parameter)를 가지고 있는 쇠마디(keyword) 길표로, 인자로 넘겨준 번호가 붙은 성분에서 글자를 뽑아내는 일을 합니다. 이 "at:" 길표는 번호 붙은 성분의 값을 뽑아낼 수 있다는 점에서 매우 중요한 역할을 수행합니다. 보는 바와 같이, 첫 번째 글자는 1, 두 번째 글자는 2, 이런 식으로 글줄에 들어 있는 글자를 마음대로 뽑아낼 수 있는 "at:" 지시는 생각보다 쓰임이 매우 많습니다.


이번에는 글줄에서 한 글자가 아닌 특정 부분을 뽑아내는 지시입니다.

'frankly' copyFrom: 2 to: 5              'rank'
'우리나라 대한민국' copyFrom: 5 to: 8   ☞ '나라'


"copyFrom:to:"는 두 개의 인자를 가진 쇠마디 지시입니다. 첫 번째 인자로 주어진 번호에서 시작하여 두 번째 인자로 주어진 번호에 이르는 글줄을 지시를 받는 글줄에서 빼내어 새로운 글줄을 만듭니다. 즉 이미 존재하는 글줄의 글줄 토막(substring)을 구하는 것입니다.


위에서 보다시피 한글로 이루어진 글줄에도 똑같이 "copyFrom:to:" 지시를 적용할 수 있습니다. 그러나 조심할 것은 번호를 셀 때 한글을 반드시 두 글자로 쳐서 계산해야 한다는 것을 있지 말아야 한다는 것입니다. 위에서 왜 "5"와 "8"이라는 번호가 쓰였는지를 생각해 보십시오.


마지막으로 글줄의 길이를 구하는 지시를 알아보겠습니다.

'hello' size             5
'우리 나라' size         9


길표 "size"는 지시를 받은 글줄의 길이를 결과값으로 남깁니다. "Hello"의 경우는 다섯 개의 글자로 이루어져 있으므로 "5"라는 값을 남기는 것은 당연하겠지요?


그런데 "우리 나라"의 경우는 한글 한 글자가 두 개의 영문자로 이루어져 있다고 했으므로, 한글 네 글자와 빈 칸 하나를 더하면 정확히 아홉 개의 글줄로 이루어져 있는 것입니다. 따라서 "9"를 결과값으로 남겼습니다.


지금까지 알아본 ",", "at:", "copyFrom:to:", "size"는 모두 글줄을 다룰 때 기본적으로 꼭 필요한 기능을 제공하는 아주 중요한 길표들입니다. 그런데 만약 다음과 같이 조금은 엉뚱한 실험을 해 봅시다. 아래 세 개의 글토막을 하나씩 가늠해 보면, Smalltalk는 과연 어떤 반응을 보이겠습니까?

1000, '원' 
'no' at: 3 
'yes' copyFrom: 2 to: 4


한결같이 발자취 창(walkback window)을 띄우는, 즉 잘못된 지시들입니다. 그럼 Smalltalk가 위의 지시를 제대로 처리하지 못한 것은 무엇 때문이었을까요? 하나씩 차례대로 따져 봅시다.

1000, '원'


위의 경우는 "SmallInteger does not understand #,"라는 알림글을 내놓았습니다. 즉 작은 정수 갈래에 속하는 "1000"은 아예 "," 지시를 알아듣지 못하는 것입니다. 만약 위의 경우에 '1000원'이라는 글줄을 만들고 싶으면 다음처럼 글토막을 고쳐야 합니다.

'1000', '원'   '1000원'


즉 정수 객체가 "," 지시를 알아들을 수 없기 때문에 글줄 객체를 사용해야 한다는 것입니다.

'no' at: 3


위의 경우는 "Index 3 is out of bounds"라는 알림글을 받습니다. 이는 "3 번은 범위를 넘어간 값입니다" 라는 뜻인데, 위의 경우 글줄은 두 개의 성분을 가지고 있으므로 당연히 "3"번 성분은 존재하지 않는 것입니다. 따라서 'no'라는 글줄이 "at:"이라는 지시는 이해했지만, 인자로 잘못된 값을 넘겨주었기 때문에 처리할 수 없었던 것입니다.

'yes' copyFrom: 2 to: 4


위의 경우도 마찬가지입니다. 'yes'라는 글줄에게 두 번째부터 네 번째까지의 성분을 뽑아내 달라고 하는 것인데, 글자 세 개로 이루어진 글줄이므로 눈을 씻고 찾아봐도 "4"번 성분은 없는 것입니다. 역시 'yes' 객체는 우리가 전달한 "copyFrom:to" 지시를 알아듣기는 했지만 두 번째 인자가 잘못된 번호를 가지고 있기 때문에 글토막을 제대로 가늠할 수 없었던 것입니다.


이왕 실험을 시작했으니 다음과 같은 글토막을 펴보고 어떤 결과를 남기는지 지켜봅시다.

'me', '', 'et'                   'meet'       "(1)"
'yes' copyFrom: 2 to: 2          'e'          "(2)"
'yes' copyFrom: 2 to: 1          ''           "(3)"


위의 세 개의 글토막은 모두 제대로 실행되어 나름대로 결과값을 남겼습니다. 앞에서 했던 것처럼 이번에도 하나씩 따져가면서 생각해 봅시다.


(1)의 경우는 보통 글줄 사이에 빈 글줄이 들어가 있었고, 이 세 개를 하나의 글줄로 만드는 것입니다. 빈 글줄은 당연히 아무것도 없기 때문에 빈글줄을 더한대도 결과는 변하지 않습니다. 즉 'me'와 'et'라는 두 개의 글줄을 더한 것과 같은 결과를 남긴다는 것입니다. 이는 "3 + 0 + 4"는 "3 + 4"와 같은 결과를 내는 것과 같다고 할 수 있습니다. 빈 글줄은 수에서의 "0"과 상등한 의미를 가집니다.


(2)의 경우는 "yes" 객체에게 두 번쨰에서 시작해서 두 번째에 이르는 글줄을 꺼내달라고 지시하는 것인데, 따라서 "copyFrom:to:"는 정확히 두 번째 글자를 글줄로써 되돌려 주었습니다. (2)번 글토막이 "at:"과 다른 점이 있는데, 그것은 바로 "at:"의 경우는 글줄에서 한 개의 글자를 뽑아내는 것이었지만, "copyFrom:to:"에 (2)와 같은 인자를 주면 한 개의 글자가 담긴 __글줄__을 결과값으로 남긴다는 것이 다른 것입니다.


(3)의 경우는 조금 이상하다는 생각이 들것입니다. 인자로 주어진 "2"나 "1"은 모두 올바른 번호들이지만, 시작과 끝이 서로 바뀌어 있습니다. 처음보다 나중의 번호가 더 크기 때문에, 결국 아무 것도 없는 빈 글줄을 되돌렸습니다. 따라서 "copyFrom:to:"에 인자를 줄 때에는 처음값 보다는 끝값이 항상 커야 한다는 것을 알 수 있습니다.


이제 우리는 Smalltalk에서 글줄을 어떻게 나타내고, 이런 글줄이 어떤 일을 할 수 있는지를 알아보았습니다. 그런데 한결같이 글줄은 작은따옴표를 달고 다닙니다. 그럼 큰따옴표(" ")는 쓸데가 없는 것일까요? 그렇지 않습니다. Smalltalk에서 작은따옴표가 글줄을 나타내는 중요한 일을 하듯이, 큰따옴표(" ")역시 매우 중요한 일을 합니다. 이 마디를 닫기 전에 마지막으로 큰따옴표의 쓰임에 대해서 알아보도록 하겠습니다.


다음 글토막을 가늠해 보십시오.

"hello"                  nil  (1)
1000 "원"                25   (2)
1 "and" + "and" 2        3    (3)


Smalltalk는 큰따옴표(" ") 안에 들어있는 모든 것을 __무시합니다.__ 즉 큰따옴표 안에 어떤 글줄이 들어가 있던지 Smalltalk는 이것을 무시하고 아무 것도 없는 것으로 생각합니다.


그래서 (1)의 경우에 Smalltalk는 아무런 글토막도 없다고 생각하여 헛(nil)을 결과값으로 남겼습니다. (2)의 경우 Smalltalk는 25라는 값은 알아듣지만 바로 뒤에 있는 "원"은 큰따옴표에 둘러싸여 있으므로 역시 무시해 버립니다. 그래서 결과값으로 25만 덩그러니 나온 것입니다. (3)의 경우 역시 글토막 중간에 있는 "and"는 Smalltalk 에 있어서는 아무런 의미가 없는 것이기 때문에, 결과값으로 "3"을 나타낸 것입니다.


이렇게 Smalltalk가 큰따옴표 안에 있는 모든 것을 무시해 버리기 때문에 우리는 이런 특성을 이용해서 바탕글에 풀이글(comment)을 달아둘 수 있습니다. 풀이글이란 말 그대로 바탕글에 있는 어떤 사실을 풀어 쓴 글을 말하는데, 이는 나중에 바탕글을 읽을 때 도움을 주기 위한 것입니다. 다음 글토막을 펴 보십시오..

$a isLetter   true
$1 isLetter   false


여기서 "letter"라는 낱말을 어떤 사람은 "글자" 라고 읽기도 할 것입니다. 따라서 위와 같은 결과가 나오게 되면 $a 와 $1 은 모두 다 글자인줄 알았는데, 결국 $1은 글자가 아니라는 결론을 내어버릴 수도 있는 것입니다. 그러나 여기에 다음처럼 풀이글을 달아두면 어떨까요?

"isLetter는 지시를 받는 객체가 영문자인지를 가려낸다."
$a isLetter   true
$1 isLetter   false


Smalltalk는 어차피 큰따옴표 안에 있는 모든 것을 무시해 버리기 때문에 위와 같이 해 두면 결국 바탕글을 쉽게 읽을 수 있는 것입니다. 물론

1 + 2    3    "1과 2를 더하는 글토막"


처럼 너무나 당연한 것인데 일일이 풀이글을 달아준다고 해서 다 좋은 것은 아닙니다. 풀이글은 꼭 필요할 때 달아주는 것이 중요합니다. 지금이야 우리가 작은 글토막들을 다루고 있지만, 앞으로 우리가 큰 바탕글을 써 내려갈 때쯤이면 이 풀이글의 중요함을 더욱 더 절실하게 깨달을 수 있을 것입니다. 아울러 Dolphin Smalltalk에서는 풀이글을 초록색의 기울임 글씨로 나타내어 다른 글토막과 쉽게 구분할 수 있도록 하는 문법 돋이(syntax highlighting) 기능을 제공하고 있습니다. 이런 기능은 매우 바탕글을 쉽게 읽을 수 있도록 도와줍니다.


이제 우리는 글자와 글줄에 대해서 알아보았습니다. 글자는 "asciiValue" 라는 이름 붙은 성분을 가지고 있었고, 동시에 글줄(string)을 이루는 매우 중요한 원소였습니다. 글줄은 번호 붙은 성분을 가지고 있었으며, 여러 가지의 지시를 통해서 많은 일을 할 수 있다는 것을 알아보았습니다. 글자와 글줄이 중요한 이유는, 프로그램을 실행하고 난 다음에 생기는 결과들을 의미 있게 나타내어주기 위해서 바로 글자와 글줄을 사용해야 한다는 것입니다. 이제 앞으로 여러 가지 프로그램을 짜 보면서 여러분은 글자와 글줄의 중요함을 알게 될 것입니다.


이제 다음부터 우리는 Smalltalk 에서 논리값을 다루는 방법에 대해서 알아볼 것입니다. 컴퓨터가 가지고 있는 중요한 특징 중에 하나를 꼽으라면 바로 "판단을 할 수 있다"는 것일 것입니다. 논리값은 바로 이런 판단의 기준이 되는 것입니다. 논리값을 다루어봄으로써 여러분은 지금과는 또 다른 객체의 세계를 접해볼 수 있을 것입니다.


진리를 찾아서...

"진리란 무엇인가?" 이 글을 읽는 여러분 중에도 한 번쯤은 이런 고민을 해본 사람이 있을 것입니다. 필자도 잘 모르겠습니다. 무엇이 진리인지. 어떤 이는 진리를 절대적인 것이라 보기도 하고, 또 어떤 이는 상대적인 것이라 보기도 합니다. "진리"(眞理)란 "참 되게 통하는 것", 즉 참된 것을 가리킵니다. 무엇이 진리인지를 알려면 우리는 언제나 "참"과 "거짓"을 가리지 않을 수 없습니다. "참되다", "거짓되다"라는 것은 이처럼 우리 생활 속에 깊이 뿌리를 내리고 있습니다. 우리는 하루에도 수 십 번 "참"과 "거짓"에 대해서 생각하고 무의식중에도 수없이 "참"과 "거짓"을 가리면서 살아갑니다. 그만큼 이 것은 우리의 생각과 행동을 결정짓는 중요한 요소라고 필자는 생각합니다.


프로그래밍에 있어서도 이러한 "참"과 "거짓"은 매우 중요한 위치를 차지합니다. 컴퓨터가 가지고 있는 특징을 나열해 보면, "계산", "기억", "판단" 이 있는데, 이 중에서 "판단"을 매우 중요하게 생각하는 것은, 이 "판단"을 통해서 컴퓨터 프로그램이 수행되고 결과를 처리하기 때문입니다. 만약 컴퓨터 프로그램이 판단을 하는 능력이 없다면, 이렇게까지 컴퓨터가 발달하지는 못했을 것입니다. 사람이 설정해 둔 여러 가지 사항을 판단하여 그에 맞는 결과를 내는 것. 이것은 컴퓨터가 나오기 이전에는 상상하기 어려웠던 일인 것입니다.


물론 컴퓨터가 내리는 판단이라는 것은 결국은 사람들이 세운 기준에 따른 것이지만, 우리가 어떤 사실을 가르쳐 준다면 컴퓨터는 그 사실들을 기억하고 있다가 참과 거짓을 가려냅니다. 이제 이번 나눔에서는 이러한 판단의 준거가 되는 "참"과 "거짓", 그리고 이를 둘러싼 여러 가지 사실에 대해서 알아보도록 하겠습니다.


논리값의 특성

Smalltalk에는 많은 객체가 있고, 각각의 객체는 저마다 알아들을 수 있는 여러 가지 지시를 가지고 있습니다. 그 중에 어떤 지시는 결과값으로 "참"이나 "거짓" 등의 논리값을 남기는 것도 있습니다. 어떤 것이 있었습니까?


앞서 정수(Integer)와 소수(Float)를 공부할 때 "=" 라는 지시를 써먹은 적이 있습니다. 기억나십니까? 아마 우리는 다음의 글토막을 폈을 것입니다.

2 = 2.0   true


정수 객체 "2"에게 "=" 지시를 보냈는데, 인자로 "2.0"을 함께 보냈습니다. 위의 물음에 정수 객체 "2"는 "참"(true)이라고 대답했습니다. 다시 말하면, "2"라는 객체는 "=" 지시의 인자로 넘어온 객체가 자신과 같은지 다른지를 알 수 있는 능력이 있는 것입니다. 이는 비단 정수 객체뿐만이 아니라 우리가 다루는 모든 객체, 즉 Smalltalk에 있는 모든 객체가 할 수 있는 기능입니다. 아래 몇 가지 보기가 있습니다.

2.0 = 2                  true
(2/4) = (1/2)            true
$a = $b                  false
$b = 2.0                 false
'hello' = 'hello'        true
'Hello' = 'hello'        false
3 class = 3.0 class      false
true = false             false


객체는 겉으로 보기에는 모양이 달라 보이더라도 그 의미가 같으면 "=" 지시에 모두 "참"으로 대답합니다. 위에서 "2.0"과 "2"는 서로 갈래가 다름에도 불구하고 "=" 지시에 의해서 "참"값을 남겼습니다. 분수 객체인 "2/4"와 "1/2"의 경우 역시 두 객체의 분모와 분자가 다름에도 "참"이라는 결과값을 남겼습니다. 이는 분수의 경우 계산을 하기 전에 언제나 약분이 된다는 것을 생각하면 쉽게 이해할 수 있을 것입니다.


물론 이는 글자(Character)의 경우도 예외는 아니어서, 보기에서와 같이 $a 와 $b 처럼 완전히 다른 글줄의 경우는 "=" 지시는 "거짓" 이라는 결과를 남깁니다.


글줄(string) 역시 "=" 지시에 똑같이 대답합니다. 자신과 같은 글줄을 인자로 가지고 "=" 지시를 보내면 당연히 'hello' 객체는 "참"으로 대답할 것입니다.


수(Number)의 경우에는 모양이 다르나 의미가 같은 경우 "=" 지시가 "참"을 남겼습니다. 그러나 글줄은 매우 엄격하게 따집니다. 글줄 'hello'와 'Hello'의 경우 우리들이 보기에는 같아 보입니다. 그러나 컴퓨터는 글줄에 들어 있는 대문자와 소문자를 엄격하게 구분합니다. 따라서 'hello' 는 글줄 첫 글자가 대문자로 시작하는 'Hello'와 서로 다르다고 보는 것입니다.


같고 다름을 판단할 수 있는 것은 갈래(class) 역시 마찬가지입니다. 갈래 또한 엄연한 객체이기 때문에 똑같이 "=" 지시를 알아들을 수 있습니다.

3 class = 3.0 class


위의 경우 왼쪽의 경우는 SmallInteger 가 되고, 오른쪽의 경우는 Float 이 됩니다. 즉 두 개의 갈래가 서로 다르기 때문에 "거짓" 값을 남기는 것입니다.


"trau"와 "false" 역시 객체이기 때문에 "=" 지시를 알아들을 수 있습니다. 위의 경우 "true"와 "false"는 둘 다 엄연히 다른 값이기 때문에 둘은 서로 다른 값을 가지고 있습니다. 결과값으로 "false"가 남겨지는 것은 당연하겠지요.


이처럼 어떤 객체에게 "="를 지시하면 인자로 넘어온 객체와 자신을 비교하여 "참" 또는 "거짓"으로 응답합니다. 이와 같이 같고 다름을 판단할 수 있는 것은 컴퓨터가 할 수 있는 기능 중에서 매우 중요한 위치를 차지합니다.


크기값과 관계 연산자

앞서 우리는 작은 정수와 큰 정수를 포함한 정수(Integer), 분수(Fraction), 소수(Float), 글자(Character)에 대한 갈래 씻줄을 알아보았습니다. 가만히 살펴보면 위의 다섯 가지의 갈래들은 한결같이 "Magnitude"라는 갈래에 씻줄로 얽혀있다는 것을 알 수 있습니다. 이 "Magnitude"는 "크기를 가지고 있는 값"이라는 뜻입니다. 즉 두 개의 값이 있을 때 그 값들 사이에 크고 작음을 가려낼 수 있다는 말입니다. 즉 앞서 "=" 지시가 같고 다름을 가릴 수 있다면 크기값에 내릴 수 있는 지시는 두 객체 사이의 크고 작음을 비교할 수 있다는 말입니다. 이렇게 크고 작음을 가려낼 수 있는 지시를 통틀어 "관계 연산자"(relational operator)라고 부릅니다.


Smalltalk에서 사용할 수 있는 관계 연산자는 다음과 같은 것들이 있습니다.

2 > 5    false        "2가 5보다 크다"
2 < 5    true         "2가 5보다 작다"
2 >= 5   false        "2가 5보다 크거나 같다"
2 <= 5   true         "2가 5보다 작거나 같다"


관계 연산자는 위에서도 볼 수 있듯이 크기값(Magnitude) 사이의 크기 관계를 비교할 때 사용합니다. ">", "<" 의 경우는 산수에서 쓰는 것과 같은 기호를 사용하고, "작거나 같다"와 "크거가 같다"의 경우는 "<=", ">=" 처럼 사용합니다. "=>", "=<" 는 사용할 수 없으므로 주의가 필요합니다. 아울러 앞서 살펴보았던 "=" 의 경우 "상등(equality)연산자" 라고 부르는데, 상등 연산자에는 "=" 말고도 "~="이라는 지시가 하나 더 있습니다.

2 = 5    false        "2와 5는 같다"
2 ~= 5   true         "2와 5는 같지 않다"


즉 "="은 왼쪽과 오른쪽이 모두 같음을 의미하고, "~="은 두 값이 서로 다르다는 것을 나타냅니다. 즉 "="와 "~="는 서로 반대되는 결과를 나타냅니다. 물론 "="와 "=~"는 상등 연산자이므로 어떤 객체에든지 적용할 수 있고, 관계 연산자의 경우는 크기값(magnitude)의 경우에만 적용할 수 있습니다.


관계 연산자는 모든 크기값에 적용할 수 있다고 했습니다. 그럼 관계 연산자를 가지고 다음과 같은 몇 가지 실험을 해 봅시다.

2.0 > 5.0                false        "(1)"
(2/4) < (3/4)            true         "(2)"
(12/43) >= (7/9)         false        "(3)"
2 > 2.0                  false        "(4)"
3 <= 3.0                 true         "(5)"
10 > (8/9)               true         "(6)"
(3/4) > 1.2              false        "(7)"


우선 (!)부터 (3)까지를 생각해 봅시다. 이 경우 소수와 소수, 분수와 분수끼리 계산이 되기 때문에 당연히 아무런 문제가 없습니다. 분수의 경우는 언제나 그렇듯이 계산을 하기 전에 약분이 이루어집니다. 따라서 (1), (2), (3) 세 경우 모두 알맞은 결과값을 얻을 수 있는 것입니다.


다음, (4)부터 (7)까지의 경우를 생각해 볼 터인데, 그 전에 앞에서 우리가 정수, 분수, 소수를 섞어서 계산했을 때 어떤 변환 규칙이 있었는지 기억하십니까? 정수, 분수, 소수가 하나의 계산식에 섞여 있으면 언제나 다음 순서로 값의 형태(=갈래)가 바뀌게 되어 있습니다.


정수(Integer) → 분수(Fraction) → 소수(Float)


(4)의 경우 계산식에 "정수", "소수"가 섞여 있으므로 앞의 "2"가 소수, 즉 "2.0"으로 바뀌어 계산됩니다. (5)의 경우 역시 "3"이 "3.0"으로 값이 바뀌어 계산됩니다. (6)은 "정수"와 "분수"로 이루어진 식이므로, "정수" 10이 "분수"로 바뀌어 계산됩니다. (7)의 경우는 "분수"와 "소수"로 이루어진 식이므로, "분수"가 "소수"로 먼저 값이 바뀐 다음에 계산이 이루어집니다.


이제 조금 더 복잡한 예를 들어볼까 합니다.

(2 + 3) < (4 + 2)                true
((2/5) + 3) >= (1.0 * 2)         true


위의 경우는 계산식을 일단 계산한 다음에 그 결과를 가지고 관계 연산자로 비교하는 글토막입니다. 역시 길표의 우선 순위에 따라서 비교할 식에는 적당히 괄호를 둘러줘야 합니다. 물론 꼭 괄호가 필요 없을 때도 있습니다. 아래의 두 경우가 그런 보기입니다.

3 < 4            true
2 + 4 < 3        false


위의 경우는 당연히 계산이 끝난 완전한 객체이므로 괄호가 필요 없습니다. 그런데 바로 아래의 경우는 계산식이 있음에도 불구하고 괄호가 필요하지 않았습니다. 왜 그럴까요? 오랜만에 Smalltalk가 평가하듯이 계산식의 흐름을 따라가 봅시다.

2 + 4 < 3 →
~~~~~
6     < 3 →
~~~~~~~~~
☞ false


즉 겹마디 지시(binary message)의 경우는 언제나 왼쪽에서 오른쪽으로 식을 가늠하기 때문에 위의 경우 정확하게 계산이 되었습니다. 그러나 아래의 경우는 잘못을 냅니다.

2 > 3 + 4                "False does not understand #+"


위의 글토막이 왜 제대로 가늠되지 못하는지 확인해 봅시다.

2 > 3 + 4
~~~~~
false + 4
~~~~~~~~~


"false" 객체는 "False"(첫 글자가 대문자) 갈래에 딸린 실체이고, 이 실체에 결국 "+"라는 지시를 보내는 격이 됩니다. 논리값은 덧셈 계산식을 알아듣지 못하기 때문에 결국 위의 글토막은 잘못된 결과를 얻을 수밖에 없습니다. 따라서 이런 경우 괄호를 적당히 둘러주면 계산식을 올바르게 가늠할 수 있는 것입니다.


앞에서 살펴본 대로 수(Number)에서는 우리가 사칙 연산(+, -, *, /)을 공부할 때와 비슷한 규칙이 적용됩니다. 그런데 크기값(magnitude)에는 숫자만 있는 것이 아닙니다. 바로 '글자'(Character)도 엄연한 하나의 크기값입니다. 아래의 글토막을 펴 봅시다.

3 isKindOf: Magnitude    true
3.0 isKindOf: Magnitude  true
$a isKindOf: Magnitude   true


위에서 볼 수 있듯이 정수(3)와 소수(3.0) 그리고 분수는 모두 크기값입니다. 또한 글자($a) 역시 크기값입니다. 따라서 $a, $b와 같은 글자에 대해서도 관계 연산자를 사용할 수 있다는 말이 됩니다.


그렇다면 Smalltalk는 글자들을 어떻게 비교하고 처리할까요? 아래의 글토막을 펴 봅시다.

$a > $b          false        "(1)"
$a < $b          true         "(2)"
$a < $A          false        "(3)"
$1 < $a          true         "(4)"
$! > $1          false        "(5)"


위의 다섯 가지의 경우를 잘 생각해 봅시다. (1)과 (2)를 생각해 보면 $a는 $b보다 작다고 나옵니다. 또한 (3)을 보면 대문자 $A가 $a보다 작다고 나옵니다. 더구나 (4)는 숫자인 $1 역시 $a 보다 작다고 알려줍니다. 마지막으로 (5)에서 $!는 $1보다도 작다고 나옵니다. 자, 도대체 Smalltalk 에서는 어떤 기준으로 글자들의 크기가 정해지게 되는 것일까요?


글자들이 서로 크기를 비교할 때에는 그들이 가지고 있는 고유 번호, 즉 코드(code)로써 비교됩니다. 앞서 글자가 가진 고유 번호는 어떤 것이라고 했습니까? 네, ASCII 코드가 그것이죠. 위의 경우를 코드를 사용해서 생각해 봅시다. 위에서 사용된 글자들을 글자의 고유 번호로 바꾸어서 나타내면 아래 글토막들을 얻을 수 있습니다.

97 > 98          false
97 < 98          true
98 < 65          false
49 < 97          true
33 > 49          false


이제 위에서 가늠해 본 글토막의 결과가 어떤 이유로 나타났는지를 짐작할 수 있을 것입니다. 이렇게 글자끼리의 비교는 글자의 번호를 사용합니다. "컴퓨터"가 "수"를 주로 다룬다고 생각하면 별로 이상할 것도 없겠지요?


공교롭게도 ASCII 코드는 사전 배열 순서와 일치합니다. 따라서 사전에서 앞에 나오는 글자는 크기가 작고, 뒤에 나오는 글자는 크기가 크다는 것입니다. $a보다는 $b가, $1보다는 $A가 사전에서 뒤에 나오기 때문에 크기가 큰 것입니다.


논리값을 남기는 다른 연산자들

지금까지 우리는 "=", "~="의 상등 연산자(equality operator)와 "<, <=, >, >="의 "관계 연산자"(relational operator)에 대해서 알아보았습니다. 이들은 모두 다른 객체들을 인자로 취하여 결과값으로 "참"이나 "거짓" 등의 논리값을 내놓는 것들이었습니다.


그런데 어떤 객체를 인자로 취하여 논리값을 남기는 연산자는 상등 연산자나 관계 연산자만은 아닙니다. 여러분도 짐작했겠지만 우리는 이미 이런 종류의 연산자들을 써 봤고 또 알고 있습니다. 다음을 보십시오.

3 isKindOf: Integer      true
$a isUppercase           false
$o isVowel               true
5 between: 1 and: 10     true


"is-"가 붙은 길표(selector)들은 대게 그 길표를 통해 지시를 받은 객체의 상태를 파악하는 "묻는 지시"(query message)인 경우가 많습니다. 이를테면 객체가 어떤 갈래 붙이인지를 알아보기 위한 "isKindOf:"라던가, 글자가 대문자인가 혹은 소문자인가 등을 조사하는 여러 지시들은 모두 "is-"로 시작하고, 따라서 해당 객체의 상태를 "참" 또는 "거짓"으로 나타냅니다.


물론 논리값을 내놓는 지시들이 모두 "is-"로 시작하지는 않는데, 가장 대표적인 것이 "between:and:" 지시입니다. 이 지시를 받은 객체는 주어진 두 개의 인자 사이에 있으면 "참" 을 남기는 지시입니다. 따라서 "between:and:" 역시 논리값을 결과값으로 남기는 연산자라고 할 수 있습니다.


Smalltalk에는 이밖에 논리값을 결과로 남기는 지시들이 많이 있습니다. 이런 것들은 상등 연산자, 관계 연산자와 함께 논리값을 다루는데 매우 중요한 역할을 하고 있습니다.


논리 연산자

논리 연산자는 지금까지 다루었던 논리값을 남기는 연산자들에 적용하여 "참"이나 "거짓" 등의 값을 다룰 수 있게 해줍니다. 여기서는 논리 연산자란 무엇이고, 그것이 왜 필요한지에 대해서 알아보도록 합시다.


산수에서 아래와 같은 조건을 본 적이 있을 것입니다.

1 < 5 < 10


이것은 "5는 1보다는 크고, 10보다는 작다"는 말입니다. 그럼 위의 문장을 Smalltalk에서 그대로 쓰면 어떻게 될까요?

1 < 5 < 10       "True does not understand #<"


짐작했겠지만 잘못이 발생했습니다. Smalltalk에서 겹마디 지시는 항상 왼쪽과 오른쪽, 이렇게 두 개씩 객체들을 취합니다. 따라서 위의 경우는

1 < 5 < 10 
true < 10 


이라는 결과가 나오는 것입니다. treu는 당연히 "<" 지시를 알아들을 수 없으므로 잘못을 발생한 것입니다. 그렇다면, 위의 경우 Smalltalk에서는 어떻게 표현해야 할까요?


위에서 우리가 살펴본 식은 두 개의 조건이 합쳐져 있다고 생각하면 됩니다.

1 < 5
5 < 10


즉 위의 두 개의 조건이 모두 참이어야만 "1 < 5 < 10"의 조건이 만족되는 것입니다. 결과적으로 두 개의 조건을 이어서 하나의 결과값을 만들어 내야 합니다. 바로 이것이 "논리 연산자"(logical operator)의 역할입니다.


다음 글토막을 펴 보십시오.

(1 < 5) & (5 < 10)       true


위에서 "&" 기호가 바로 논리 연산자의 한 종류입니다. 이 "&" 기호는 "그리고"라고 읽습니다. 굳이 영어로 하면 "and"가 되겠지요. 그리고 "&"의 연산수가 되는 두 개의 식들이 먼저 계산되어야 하기 때문에 괄호를 둘렀습니다.


Smalltalk에서 미리 마련된 논리 연산자에는 두 연산수가 모두 참일 때 결과값도 참이 되는 "논리곱"(and, 그리고)과 한 쪽의 값만 참이어도 결과값이 참이 되는 "논리합"(or, 또는)이 있습니다. 더불어 현재의 논리값을 반대로 뒤집는 "부정"이 있습니다.


아래에서 "a", "b"는 "참" 혹은 "거짓"값을 가지는 것으로 합니다.

a & b           두 값이 모두 참이면 "참", 그 밖은 "거짓".
a | b           두 값 중 하나라도 참이면 "참", 그 밖은 "거짓".
a not           a가 참이면 "거짓", 거짓이면 "참".


이 세 개의 논리 연산자들을 이용하여 여러 가지 조건을 연결해서 하나의 결과값을 나타낼 수 있습니다. 그런데 위의 연산자들이 하는 일에 대해서 매우 생소한 사람이 있을지도 모르겠습니다. 그래서 아래에 표를 통하여 결과를 정리해 놓았습니다.

값1 값1 &(논리곱) (논리합)
거짓 거짓
거짓 거짓
거짓 거짓 거짓 거짓


위의 표처럼 논리 연산자의 참과 거짓값을 규정해 놓은 표를 "진리표"(truth table)라고 부릅니다. 위의 표를 보면 "참 & 거짓" 의 경우 결과값으로 "거짓"이 나옴을 알 수 있습니다.


그럼 이제 아래의 몇 가지 글토막들을 통하여 논리합과 논리곱이 어떻게 적용되는지를 살펴보도록 합시다. 우선 "true"와 "false"를 직접 사용합시다.

true & true      true
true & false     false
true | false     true
false | true     true
false & false    false


잘 보면 위의 결과들은 앞에서 필자가 제시한 진리표와 일치한다는 것을 알 수 있습니다. 그런데 실제로 프로그램을 작성할 때에는 위와 같이 논리 연산자의 연산수로 직접 "참"이나 "거짓"을 사용하는 경우는 극히 드뭅니다. 대신 앞서 우리가 살펴본 논리값을 결과값으로 산출하는 연산자나 지시 등을 사용합니다. 그럼 이제 조금 더 복잡하면서도 일반적인 예를 들어봅시다.

(2 > 3) & (4 > 3)        false
($a isVowel) | (3 < 1)   true


식이 조금 복잡해 보이기는 하지만, 어차피 모든 길표(selector)들은 "우선권 법칙"을 따르기 때문에 차근차근 살펴보면 결코 어려운 식은 아닙니다. 위의 예에서 첫 번째 글토막을 함께 따라가며 가늠해봅시다.

(2 > 3) & (4 > 3) 
~~~~~~  
false   & (4 > 3) 
          ~~~~~~~ 
false   & true 
 false


즉 복잡한 식의 경우라도 길표의 우선권을 잘 생각하면서 가늠하면 어렵지 않게 생각할 수 있습니다. 자, 그럼 아래의 글토막은 어떤 결과를 낼까요?

(3 isKindOf: Object) & (100 factorial between: 1 and: 1000 factorial)


역시 매우 복잡해 보이는 글토막이지만 차근차근 풀어보면 결과를 알 수 있을 것입니다. (논리 연산의 법칙을 아는 사람은 위의 식을 다 가늠해 보지 않고도 결과를 알 수 있을지도 모르겠습니다. ^^:)


이제 마지막으로 "부정"에 대해서 알아봅시다. 부정은 논리값을 뒤지는 일을 합니다. 아래를 보십시오.

true not                 false
false not                true
(2 < 3) not              false
(2 > 3) & (4 > 3) not    false


논리값 객체에 "not" 지시를 보내면 지시를 받은 객체와 반대되는 값을 남깁니다. 즉 "참"은 "거짓"으로, "거짓" 은 "참" 으로 뒤바꾸는 역할을 합니다. 주로 이 "not" 지시는 어떤 논리 연산으로 나온 결과를 부정하여 조건을 세울 때 사용합니다.


지금까지 우리는 "논리합", "논리곱", "부정"의 세 가지 논리 연산자를 공부해 봤는데, 이들은 프로그램에서 여러 가지 조건을 나타내는데 사용됩니다. 이제 이렇게 해서 판가름된 진리(참이나 거짓)는 후에 프로그램의 실행 흐름을 바꾸는 매우 중요한 역할을 하게 됩니다. 프로그램의 실행 흐름에 대해서는 훗날 이야기를 나눌 기회가 있을 테니, 그 때 가서 더 자세한 이야기를 하도록 합시다.


짧은 경로 계산과 채온 경로 계산

앞서 우리는 &(논리곱)과 |(논리합)에 대해서 공부한 적이 있습니다. 그런데 이 두 개의 연산자는 독특한 특성을 가지고 있습니다. 자, 다음의 계산 결과가 무엇인지 생각해 봅시다. Smalltalk를 쓰지 말고 반드시 암산으로 풀어야 합니다.

(2 > 3) & ((3 * 13243 / 32 + 1.23 - 4) < (3 + 30000 * 2132323 - 78 * 33 )


위의 식을 푸는 사람들을 보면 대게 두 부루류 나뉘어집니다. 우선 첫 번째 부류는 한참 동안 머릿속으로 식을 계산하는 사람들이고, 두 번째는 위의 식을 보자마자 바로 결과를 말하는 사람입니다. 자, 여러분은 위의 두 부류 중에 어떤 부류입니까? 단순히 여기서는 암산의 속도 문제가 아닙니다. 위와 같은 차이가 나게 되는 이유는 다른데 있는 것입니다. 그 이유가 무얼까요?


위에서 사용된 논리 연산자는 "&", 즉 "논리곱"입니다. 논리곱은 무엇입니까? "주어진 두 개의 연산수 모두가 '참'이면 결과값도 '참'" 인 연산입니다. 이 말은, 결국 주어진 두 개의 연산수 중에 "거짓" 이 하나라도 있으면 결과 역시 당연히 "거짓" 이 된다는 말입니다.


이제 이해가 가십니까? 위의 경우 "&" 지시를 받는 객체는 (2 > 3), 즉 "false"입니다. 따라서 위의 경우는 "&" 길표에 딸려오는 인자를 생각할 필요도 없이 위 식의 결과가 "거짓"이 되는 것입니다.


그런데 Smalltalk는 불행하게도 위의 경우 왼쪽과 오른쪽 연산수를 모두 다 가늠합니다. 왼쪽 연산수를 가늠하는 것은 "&" 지시를 보낼 객체를 가늠하기 위한 것이므로 당연한 것이곘지만, 위와 같이 결과가 뻔한 경우에는 굳이 오른쪽 연산수까지 가늠해서 아까운 시간을 쓸 필요가 없는 것입니다. 물론 Smalltalk는 오른쪽에 주어진 연산수(인자)까지도 충실하게(!) 가늠하여 결과를 처리합니다.


그런데 Smalltalk에는 이렇게 충실한(나쁘게 말해서 멍청한?) 지시가 있는가 하면, 상황 판단을 아주 잘해서 계산이 필요 없을 때는 굳이 계산하려고 하지 않는 똑똑한 지시가 있습니다. 바로 "and:"와 "or:"이 그것입니다. 아래를 보십시오.

true or: [true]          true
false or: [true]         true
false and: [true]        false
true and: [false]        false


여기서 처음 보는 기호가 나왔습니다. "[ ]"은 "대괄호"라고 부르는데, 나중에 이야기하겠지만 "덩이"(block)를 나타낼 때 사용하는 기호입니다. 여하튼 앞서 살펴본 "&", "|" 지시가 겹마디 지시였다면 "and:" 와 "or:" 은 "쇠마디 지시" 입니다. 거기다가 넘겨받는 인자 역시 보통의 식이 아니라 "덩이"입니다.


"&", "|" 지시가 자신에게 주어진 연산수, 즉 지시를 받는 객체(receiver)와 인자(parameter)를 모두 가늠하는 것에 비해 "and:"와 "or:"는 지시를 받는 객체를 가늠하여 그 값에 따라 덩이(block)로 주어진 인자를 가늠하기도 하고 가늠하지 않고 넘어가기도 합니다. 즉,

false and: [?]          "?" 무엇이건 무조건 "거짓"
true and: [?]           "?" 결과가 계산식의 값이 .

true or: [?]            "?" 무엇이건 무조건 "참"
false or: [?]           "?" 결과가 계산식의 값이 .


이와 같이 주어진 모든 식을 계산하는 "&", "|" 을 "채운 경로 계산"(full circuit operation)이라 하고, 필요에 따라 주어진 인자(오른쪽 연산수)를 계산에서 제외하는 "and:", "or:" 을 "짧은 경로 계산"(short circuit operatorion)이라고 부릅니다.


채운 경로 연산과 짧은 경로 연산의 차이를 가장 잘 느낄 수 있는 것은 복잡한 연산수를 다룰 때입니다. 아래에 채운 경로 연산의 보기를 들겠습니다.

(2 > 3) & (1000 factorial between: 100 factorial and: 10000 factorial)  false


위의 글토막을 가늠하려면 대략 2~4 초 정도의 시간이 필요합니다. 그것은 "&" 의 인자를 계산하기 위해서 큰 수의 계승(factorial)을 구해야하기 때문입니다. 그런데 다음 식을 가늠해 봅시다.

(2 > 3) and: [100 factorial between: 100 factorial and: 10000 factorial]  false


위의 경우 계산에 필요한 시간은 매우 짧습니다. 거의 Ctrl-DWorkspace > Display It 메뉴를 실행한 것과 동시에 결과가 나왔습니다. 무엇이 이렇게 계산 시간의 차이를 가져왔을까요? 네, 바로 "짧은 경로 계산"의 똑똑함 때문입니다.


앞서 필자가 낸 문제를 풀 때 여러분은 짧은 경로 계산을 햿습니까, 아니면 채운 경로 계산을 했습니까? ^^: 물론 짧은 경로 계산이 시간을 단축할 수 있으므로 효율적인 방법이 되기는 하지만, 반드시 가늠해야 할 지시가 들어있는 식의 경우는 오히려 채운 경로 계산이 좋습니다. 덧붙이자면 채운 경로 계산을 사용한 글토막이 짧은 경로 계산을 쓴 글토막보다 읽기가 좀 더 좋습니다.


지금까지 우리는 Smalltalk 에서 논리값을 다루는데 필요한 여러 가지에 대해서 알아보았습니다. 상등 연산자, 관계 연산자를 비롯하여 Smalltalk 에는 객체들을 이용하여 참이나 거짓을 얻는 많은 종류의 지시가 존재합니다. 또한 이렇게 얻어진 결과값을 엮어서 하나의 조건을 만드는 논리 연산자가 있었습니다. 물론 이러한 논리 연산자는 그들이 가지는 특성에 따라, 주어진 모든 연산수를 가늠하는 "채운 경로 연산"과 필요할 경우에는 연산수를 가늠하는 "짧은 경로 연산" 이 있다는 것도 알아보았습니다.


지금은 이 논리와 진리에 대해서 여러분이 그 유용성을 깨닫지 못할지도 모르곘습니다. 그러나 이렇게 익혀둔 진리 찾기는 앞으로 여러분이 프로그래밍에서 문제를 풀기 위해서, 또한 컴퓨터에 생각할 수 있는 힘(!)을 불어넣어주기 위해서 반드시 필요한 것입니다. 사실 Smalltalk 의 객체들이 똑똑하게 자신이 해야 할 일을 제대로 할 수 있는 이유 또한, 이런 객체들이 언제나 진리를 찾아서 자신히 가야할 바른 길을 가기 때문입니다. 우리도 언제나 이런 마음가짐으로 프로그램을 짜고, 공부하고, 우리들의 생활 속에서 살아가다보면, 훌륭한 하나의 객체로써, 또한 전체를 이루는 중요한 요소로써 자리매김할 수 있을 것 같습니다.


무른값, 굳은값, 그리고 바로값

Smalltalk 세상에는 많은 객체들이 존재합니다. 아니, Smalltalk 자체가 바로 객체이며, 보잘것없다고 생각되는 작은 수 하나라도 Smalltalk 를 이루고 있는 중요한 객체입니다. 이 객체는 그들이 가지는 특성에 따라 여러 가지로 분류할 수 있는데, 이번 나눔에서는 객체들의 변화성에 따라서 분류해 보고, 새로운 특징을 지니는 두 갈래의 객체에 대해서도 알아보도록 하겠습니다.


굳은값과 무른값

Smalltalk에 있는 모든 객체는 그 자신이 가지고 있는 값이 바뀔 수 있는 "무른값"(mutable object)과 어떤 경우에도 객체가 가지는 값이 바뀔 수 없는 "굳은값"(immutable object)으로 나뉘어집니다.


예를 들어 작은 정수(SmallInteger) "3"을 생각해 봅시다. 3은 3이지 결코 4나 5가 될 수 없습니다. 어떤 이는 "3 + 1"이라는 글토막을 가늠하면 결과값으로 "4"를 얻을 수 있다고 하겠지만, 이는 "3"이 "4"로 바뀐 것이 아닙니다. 결국 "3" 객체가 "+ 1"이라는 지시를 이해하고 실행하여 "3"과는 __전혀 다른__ "4"라는 객체를 새로 만들어 내는 것이지, "3"이라는 객체 자체의 의미가 바뀌는 것은 아니라는 말입니다.


반면 앞에서 살펴본 글줄(string)의 경우는 필요하면 언제든지 글줄을 구성하는 글자들을 바꿀 수 있습니다.

'Hello' inspect


위처럼 해서 객체 탐색기를 열고, 여기서 마음대로 글줄의 글자를 바꾸고 나면 실제로 글줄 자체의 값이 달라지게 됩니다. 지금 열려있는 객체 탐색기에서 두 번째 글자 $e를 $a로 바꾸어 봅시다. 앞서 "글자와 글줄"에 대해서 알아볼 때 탐색기에서 분명히 글줄을 구성하고 있는 글자 성분을 바꿀 수 있었습니다. 그 때를 기억하면서 다시 한 번 아래에 'hello'를 'hallo'로 바꾸는 과정을 적어봅니다.

  1. 왼쪽 널(pane)에 있는 선택 막대를 "1"로 옮겨서 1번 성분이 표시되게 한다.
  2. 오른쪽 널에 $e가 표시되었는지 확인한다.
  3. $e에서 'e'를 지우고 $a로 바꾼다.
  4. 오른쪽 널의 변경을 적용하기 위하여 Ctrl-S 를 누르거나 Workspace > Accept 메뉴를 실행한다.


위의 과정을 거친 뒤 실제로 글줄이 바뀌었는지를 확인하기 위해서 왼쪽 널에 있는 "self"로 선택 막대를 옮겨보면 오른쪽 널에 'hallo'가 표시되는 것을 볼 수 있습니다. 이처럼 무른값은 객체의 성분을 바꿀 수 있습니다.


그런데 Smalltalk 에는 글줄과 비슷한 속성을 가지고 있으면서도 동시에 정수(Integer)와 같은 굳은값의 성질을 갖는 객체가 있습니다. 이런 갈래의 객체를 "이름값"(symbol)이라 합니다. 즉, 이름값은 글줄의 성질을 가지고 있지만, 그 글줄을 구성하는 성분의 값을 바꿀 수 없는 굳은값인 것입니다.


아래에 이름값의 몇 가지 보기를 들겠습니다.

#Smalltalk
#Hello
#class
#isKindOf:
#between:and:
#+


이름값은 위에서 보다시피 우물 정(#)으로 시작해서 빈칸으로 끝납니다. 즉 우물 정이 이름값의 시작표시가 되고 빈칸이 나오기까지의 모든 글자들을 이름값으로 삼습니다. 그러므로 이름값에는 빈칸이 들어갈 수 없겠지요? 나중에 좀 더 자세하게 알아보겠지만, 이름값(symbol)은 Smalltalk 언어에서 사용하는 이름을 저장할 때 쓰입니다. 이를테면 #class, #isKindOf: 등은 모두 객체에게 보내는 지시(message)의 이름들입니다. 이런 이름들을 나타내기 위한 것이 주로 이름값이 하는 일입니다.


이름값에 대해서 몇 가지 실험을 해 보겠습니다.

#hello class             Symbol
Symbol superclass        String


위의 글토막들을 가늠해보면 흥미로운 사실을 알 수 있습니다. 즉 이름값의 윗갈래는 결국 글줄이라는 것입니다. 앞서 우리가 갈래에 대해서 이야기할 때를 기억해 봅시다. SmallInteger, LargeInteger 는 모두 Integer 의 아랫갈래였고, 이들 갈래의 윗갈래는 Number였습니다. 그렇다면 SmallInteger도, LargeInteger 도 결국은 Number 가 되는 것입니다.

3 class                          SmallInteger
300 factorial class              LargeInteger
3 isKindOf: Number               true
300 factorial isKindOf: Number   true


그렇다면 아래의 글토막을 가늠해 봅시다.

'hello' class            String
#hello class             Symbol
#hello isKindOf: String  true


결국 이름값은 '굳은값'이라는 특성을 갖는 글줄(string)의 한 종류라는 것을 위의 실험에서 알 수 있습니다.


지금까지 제가 계속 이름값은 굳은값이라고 했는데, 그럼 실제로 실험을 통해서 알아보도록 합시다. 다음과 같이 해 봅시다.

#hello inspect


위의 글토막을 가늠하면 객체 탐색기가 열릴 것입니다. 이제 왼쪽 널에서 두 번째 성분을 가리키는 "2"를 선택합시다. 그러면 오른쪽 널에는 $e가 나타날 것입니다. 이제 오른쪽 널에 있는 $e를 $a로 바꿉시다. 그런 다음 변경된 내용을 Smalltalk 에게 알리기 위해 Ctrl-S 글쇠를 누릅시다. 글쇠를 누르자 마자 다음 그림과 같이 Smalltalk가 불평을 늘어놓을 것입니다.

그림 2-18::탐색기에서 #hello의 성분을 바꾸려는 모습 (Stp2-18.gif)


객체에게 알아들을 수 없는 지시를 내렸을 때 나타났던 것처럼 이번에도 발자취 창(walkback window)이 열리면서 탐색기에서 우리가 취한 동작에 대해서 "should not implement"라고 투덜거리는 Smalltalk 를 볼 수 있을 것입니다. 결국 우리가 굳은값인 이름값의 성분을 바꾸려고 하면 Smalltalk는 해당 기능을 실행할 수 없다는 표시로 발자취 창을 열어 보인 것입니다.


잠시 발자취 창을 닫지 말고 중간쯤에 여러 줄로 나타나 있는 글줄 묶음을 유심히 살펴봅시다.

Symbol(Object)>>error:
Symbol(Object)>>shouldNotImplement
Symbol>>at:put:                
SequenceableCollectionInspector>>setField:to:
SequenceableCollectionInspector(Inspector)>>accept
SequenceableCollectionInspector(Presenter)>>performCommand:
CommandQuery>>perform
...(생락)...


이 글줄들은 앞의 "1.5.9" 도막에서도 이야기했듯이 Smalltalk가 잘못이 일어나기까지 어떤 길수(method)들을 실행했는지를 보여주고 있습니다. 이 길수들의 자취를 읽을 줄 알면 문제의 원인을 쉽게 파악할 수 있습니다.


위에서 화살표(←)를 달아놓은 줄을 봅시다.

Symbol>>at:put:


Smalltalk에서 어떤 갈래에 들어 있는 길수를 표시할 때에는 위와 같이 "갈래이름>>길수이름" 과 같이 표시합니다. 따라서 위의 경우에는 "Symbol"이라는 갈래의 "at:put:"이라는 길수를 나타냅니다. "at:put:"은 앞서 글줄을 설명할 때 나왔던 쇠마디 길수(keyword message)였습니다. 이를테면

'hello' at: 2 put: $a


와 같은 지시를 내리면 'hello'가 'hallo'로 바뀝니다. 그런데 지금까지 늘 이야기했듯이 이름값은 굳은값이기 때문에 at:put: 지시를 내릴 수 없는 것입니다. 결국 이름값은 그 성분을 바꿀 수 없는 굳은값이라는 것이 증명됩니다. 이제 이름값의 특성을 알았으니, Terminate 단추를 눌러서 열려 있는 발자취 창을 닫으십시오.


Smalltalk 에 관한 글에서 길수(method)의 이름을 쓸 때에는 이름값의 형태로 나타내는 관습이 있습니다. 지금부터 필자도 이 관습을 존중하여, 앞으로 글에서 길수의 이름이 나오게 되면 이름값의 형태로 적겠습니다. 이를테면 객체 3에 #class 지시를 보낸다는 식으로 적을 것입니다. 처음에는 이상해 보이겠지만, 익숙해지면 길수의 이름을 매우 쉽게 알아볼 수 있을 것입니다.


이름 짓기

이름값 이야기가 나온 김에 여기서 Smalltalk에서 이름을 붙이는 방법에 대해서 이야기를 해 볼까 합니다.


지금까지 우리들은 많은 이름들을 접해왔습니다. 그 중에는 Object, Number, Integer, String 처럼 갈래의 이름도 있었고, #class, #superclass, #isKindOf:, #between:and: 처럼 길수의 이름도 있었습니다. 이러한 모든 것은 Smalltalk에서는 이름값으로 처리되며, 이름값은 나름대로의 규칙을 가지고 있습니다.


쉽게 생각합시다. 아기가 태어나면 이름을 지어줍니다. 그런데 이름을 아무렇게나 지어주는 부모는 없을 것입니다. 왜냐하면 이름은 그 아이의 일생에 큰 영향을 주기 때문입니다. 따라서 매우 신중하게, 그리고 어떤 규칙을 가지고 이름을 지어줍니다. 보통 사람의 이름은 "성씨"와 "이름"으로 나뉘어집니다. 저의 경우에 성씨는 김(金)이고 이름은 찬홍(燦洪)입니다. 물론 요즘에는 우리말 이름을 지어주는 경우도 많이 볼 수 있지만, 그래도 역시 이 "성씨" 와 "이름" 의 관계를 무시하지는 않습니다.


마찬가지로 Smalltalk 에서도 이름을 짓는데 나름의 규칙이 있습니다. 이 규칙을 특별히 "이름 짓기 규칙"이라고 부르며, Smalltalk에서 쓰이는 모든 이름들에 적용됩니다. 아울러 이 이름짓기 규칙이 바로 이름값을 만드는데 필요한 규칙이기도 합니다.


아래에 Smalltalk의 이름 짓기 규칙을 설명합니다.

이름 짓기 규칙


  1. 이름에는 기본적으로 영문 알파벳과 숫자, 그리고 밑줄(_)을 사용한다.
  2. 첫 글자가 숫자여서는 안 된다.
  3. 영문 알파벳의 경우는 대문자와 소문자가 엄격하게 구별된다.
  4. 겹마디(binary) 지시에 한해서 다음의 기호를 사용할 수 있다.
    ! @ % * ( [ { } < > = | \ / ? + - :
    


처음에는 위의 규칙들이 조금 딱딱하게 느껴지겠지만 Smalltalk로 많은 글토막을 가늠해보고 또한 여러 가지 프로그램을 작성하다보면 어느새 위의 규칙 자체가 큰 걸림돌이 되지는 않을 것입니다.


위의 규칙에 더해서 몇 가지 이야기를 해 둘 것이 있습니다. 우선 영문자의 대소문자 구별에 대한 이야기입니다. 앞서 이야기한 바와 같이 Smalltalk에서는 철저하게 영문자의 대문자와 소문자를 구별합니다. 그래서 만약 Smalltalk 와 smalltalk 라는 두 개의 이름이 있다면 두 개는 서로 다르게 취급됩니다. 마찬가지로 SmallInteger와 Smallinteger 역시 서로 다른 이름입니다. 여러분이 실수로

3 isKindOf: Smallinteger


위처럼 했을 경우 Smalltalk는 "Undeclared Smallinteger"라는 잘못알림글을 뿌릴 것입니다. 이처럼 Smalltalk에서는 이름에 대소문자를 구별하기 때문에 특히 대소문자가 섞여있는 이름을 입력할 때 철자를 틀리지 않도록 조심해야합니다.


이제 아래에 옳은 이름과 잘못된 이름의 보기를 들고 이름 짓기에 대한 이야기를 마무리할까 합니다.

정확한 이름의 보기

a
A
IAmABoy         YouAreAGirl     Hello_Smalltalk         The3rdRoad
class           isKindOf:       between:and:            do:to:by:
+               ++              -                       --
**              &               ~=                      !=


잘못된 이름의 보기

1Copy                           숫자로 시작할 수 없다.
Good Name                       이름에는 빈칸을 사용할 수 없다.
Good!                           숫자, 영문자 및 밑줄(_)만 쓸 수 있다.
+++                             특수기호는 두 개 까지만 사용할 수 있다.


밑줄 이야기


전통적으로 Smalltalk 에서 이름을 지을 때에는 철저히 영문자와 숫자만을 사용했습니다. 그러나 최근에 발표된 Smalltalk들은 대부분 영문자, 숫자와 함께 밑줄(_)까지 이름 짓기에 사용할 수 있게 되었습니다. 이는 C/C++ 등 다른 언어에서 만들어진 부분들을 불러서 쓰거나, 반대로 Smalltalk에서 만든 것을 다른 언어에서 쉽게 사용할 수 있도록 해 줍니다. 아무튼 Dolphin 의 경우는 밑줄(_)까지 이름에 쓸 수 있기 때문에 매우 바탕글을 쉽게 알아볼 수 있습니다.


배열

앞에서 우리는 글줄(String)과 이름값(Symbol)을 공부했습니다. 이들은 모두 글자로 이루어진 번호 붙은 성분(indexed part)을 가지고 있습니다. 그런데 Smalltalk에서는 글자뿐만 아니라 서로 다른 갈래의 객체를 성분으로 가질 수 있는 핫객체도 있습니다. 이런 갈래의 객체들을 우리는 배열(array)이라 부릅니다. 즉 글줄이나 이름값은 글자로만 이루어져 있지만 배열은 그 성분에 어떠한 객체라도 다 담을 수 있는 것입니다. 다음 글토막을 펴봅시다.

#(1 2 3) class   Array


위에서 #(1 2 3)은 1, 2, 3이라는 세 개의 정수 갈래 객체를 담고 있는 배열입니다. 이 배열에 #class 지시를 보내면 "Array" 라고 응답합니다.


그러면 배열의 속은 어떻게 생겼는지 역시 객체 탐색기를 이용해서 알아보도록 합시다. 다음 글토막을 가늠해 보십시오.

#(1 2 3) inspect


그림 2-19::배열 #(1 2 3)을 탐색하는 모습 (Stp2-19.gif)


탐색기의 왼쪽 널에는 현재 탐색하고 있는 객체 자신을 나타내는 "self" 와 객체의 번호 붙은 성분인 "1", "2", "3"이 나타나 있습니다. 각각의 성분을 마우스로 눌러 보면 오른쪽 널에 해당 성분에 들어 있는 값을 확인할 수 있습니다.


이렇게 배열값을 나타내려면 #( ) 속에 필요한 성분을 넣으면 됩니다. 성분과 성분 사이는 빈칸으로 갈라줍니다.


배열에는 여러분이 생각하고 있는 모든 객체들이 다 들어갑니다. 객체 탐색기를 이용해서 아래 배열들을 탐색해 봅시다. 글쇠판에서 Ctrl-I 를 누르면 바로 객체 탐색기가 열린다는 것은 잊어버리지 않으셨지요?

  1. #($a $b $c)
  2. #(3.14 1.414)
  3. #('우리 나라' '대한 민국')
  4. #(true false nil)
  5. #(#red #blue #green)
  6. #(apple banana true)
  7. #(100 $a 'hello' true false nil #red #green #blue black )


편의상 각 배열값에 대해서 번호를 붙였습니다. 앞으로 이 번호를 가지고 이야기를 진행할까 합니다.


(1)의 경우는 $a $b $c, 이렇게 세 개의 글자로 이루어진 배열입니다. 배열의 성분이 글자라고 해서 이 배열이 곧 글줄(String)이 될 수는 없습니다. 왜냐하면 글줄과 배열은 겉모양이 분명히 다르기 때문입니다. 어쨌든 (1)은 글자들로 이루어진 배열입니다.


(2)의 경우는 실수(Float) 두 개로 이루어진 배열입니다.


(3)은 글줄 두 개로 이루어진 배열입니다. 배열의 성분을 가르기 위해서 빈칸이 쓰였지만, 글줄을 나타내는 따옴표(' ') 속에 들어 있는 빈칸은 배열에 상관없이 글줄을 이루는 성분이 됩니다. 즉, 위의 경우 '우리 나라'와 '대한 민국'이라는 두 개의 글줄을 성분으로 갖게 됩니다.


(4)는 논리값인 "참"(true)과 "거짓"(false), 그리고 아무런 의미도 가지고 있지 않은 "헛"(nil)으로 이루어진 배열값입니다. 논리값과 헛 역시 배열의 성분으로 사용할 수 있습니다.


(5)는 방금 알아본 이름값(Symbol)으로 이루어진 배열입니다. 역시 세 개의 이름값이 성분으로 들어 있습니다.


(6)은 조금 특별한 경우입니다. 여기에는 apple, banana 그리고 true 라는 세 개의 성분이 있습니다. 여기서 true 는 논리값의 "true" 를 가리킵니다. 그러나 apple이나 banana는 Smalltalk에서 미리 어떤 의미를 부여한 값이 아니기 때문에 여기서는 이름값(symbol)으로 처리됩니다. 탐색기를 통해 보거나 글토막으로 펴 보면 #(#apple #banana true) 로 나옵니다. 즉 apple 과 banana 는 이름값으로 처리되었다는 것을 보여줍니다.

#(apple banana true)     #(#apple #banana true)


(7)의 경우는 많은 수의 성분을 가진 배열입니다. 정수 100, 글자 $a, 글줄 'hello', 논리값인 true 와 false 아무런 의미도 없는 헛(nil), 그리고 이름값인 #red #green #blue #black, 이렇게 모두 열 개의 성분을 가지고 있습니다. 배열의 성분이 많다고 해도 각각의 성분을 빈칸으로 제대로 갈라주기만 하면 얼마든지 많은 수의 성분을 배열에 나타낼 수 있습니다.


배열 성분에 대한 해석


  1. ( )을 사용하여 배열값을 만들 때 어떤 성분이 들어갈 수 있는가에 대한 문제는 생각보다 복잡합니다. 왜냐하면 각 Smalltalk 시스템마다 조금씩 다르게 배열 성분을 처리하기 때문입니다.


Dolphin Smalltalk의 경우에는 앞에서 이야기한 대로 true, false, nil 을 제대로 처리합니다. 즉 배열값을 만들 때 true, false, nil 을 정확하게 논리값인 참, 거짓, 그리고 헛(nil)으로 처리한다는 말입니다.


그렇지만 Smalltalk Express 와 Smalltalk-MT 에서는 #( )안에 있는 모든 영문자 낱말들을 이름값으로 처리해 버립니다. 즉 #(true false nil)이 #(#true #false #nil)과 똑같은 의미로 쓰이게 됩니다.


이를 알아보기 위해서 아래 글토막을 각각 Dolphin Smalltalk, Smalltalk Express, 그리고 Smalltalk-MT 에서 펴보았습니다.

바탕글 1::배열 성분의 갈래들을 알아보기

#(true false nil) do: [ :element |
        Transcript cr.
        element printOn: Transcript.
        Transcript nextPut: $ .
        element class printOn: Transcript.
].


[실행 결과]
(Dolphin Smalltalk)
true True
false False
nil UndefinedObject

(Smalltalk-MT)
#true Symbol
#false Symbol
#nil Symbol

(Smalltalk Express)
true Symbol
false Symbol
nil Symbol


위의 [실행 결과] 에서 살펴볼 수 있듯이 Dolphin을 제외한 다른 Smalltalk 시스템에서는 #( )안에 들어 있는 영문자 낱말을 모두 이름값으로 간주합니다. 어느 것이 더 직관적인지는 개인에 따라 다르겠으나, 제 생각에는 Dolphin Smalltalk 의 처리방법이 좀 더 타당하지 않을까 생각합니다. 만약 여러분이 배열값을 이용하여 프로그램을 작성한다면 이러한 Smalltalk간의 사소한 차이를 제대로 간파하고 있어야 할 것입니다.


모듬 갈래의 씻줄

이번 마디에서 우리는 이름값(Symbol)과 배열(Array)이라는 두 갈래의 새로운 객체를 알아보았습니다. 이름값이 성분을 바꿀 수 없는 굳은값(immutable object)인데 반해 배열은 성분을 마음대로 바꿀 수 있는 무른값(mutable object)이고, 더구나 성분으로 어떤 갈래의 객체이든 마음대로 담을 수 있는 유연함을 가지고 있습니다. 그럼 이 두 가지의 객체들에 대해서 조금 더 깊이 생각해 보도록 합시다.


앞서 수(Number)에 대해서 이야기할 때, 우리는 #class 와 #superclass 라는 두 개의 지시를 사용해서 갈래의 씻줄을 더듬어 올라간 적이 있습니다. 마찬가지로 여기서는 이름값과 배열의 갈래 씻줄을 더듬어 올라가 봅시다. 아래의 글토막을 펴 봅시다.

#hello class                     Symbol
Symbol superclass                String
String superclass                ArrayedCollection
ArrayedCollection superclass     SequenceableCollection
SequenceableCollection superclass        Collection
Collection superclass            Object


생각보다 이름값(symbol)의 갈래 씻줄이 길다는 것을 알 수 있습니다. 갈래 씻줄 중간에 걸친 갈래들은 모두 "2.16" 에서 글줄을 이야기할 때 다루었습니다. 어차피 이름값은 글줄의 아랫갈래이므로 결국 이름값은 글줄의 씻줄을 그대로 달고 올라가는 것입니다.


앞에서 살펴보았듯이 이름값은 결국 글줄이므로, 글줄이 할 수 있는 대부분의 일을 할 수 있습니다. 이는 객체의 상속성(inheritance) 때문에 가능하게 되는 것인데, 상속성에 대해서는 나중에 따로 이야기할 기회가 있을 것입니다. 아무튼 글줄이 어떤 지시를 알아듣는다면 이름값 역시 글줄의 한 종류이므로 대부분의 지시를 알아듣고 처리할 수 있습니다. 그럼 이를 증명하기 위해 지금부터 몇 개의 글토막을 펴 보도록 합시다.

#hello = #hello          true
#hello = 'hello'         false


먼저 첫 번째 글토막을 봅시다. "=" 왼쪽의 #hello 와 오른쪽의 #hello 는 어디를 봐도 똑같은 객체입니다. 따라서 #hello 는 #= 지시에 대해서 당연히 "참"이라고 응답합니다. 그런데 두 번째의 경우는 조금 생각을 해 보아야 합니다. #hello 와 'hello' 는 똑같이 $h, $e, $l, $l, $o 라는 다섯 개의 글자로 이루어진 객체이지만, 두 객체를 비교했을 때에는 "거짓"이라는 결과값이 나왔습니다. 즉 같은 글자로 이루어진 성분이라도 글줄은 글줄이고 이름값은 이름값이라는 말입니다.


그럼 몇 가지의 실험을 더 해 보도록 합시다. 아래 글토막들을 펴 보십시오.

'hello' = #hello                 false
#hello = #bye                    false
#hello , #bye                    'hellobye'
#hello at: 1                     $h
#hello copyFrom: 1 to: 4         'hell'
#hello size                      5


위의 결과를 살펴보면 글줄(string)이 알아들을 수 있는 대부분의 지시를 이름값도 알아듣는다는 것을 알 수 있습니다. 그러나 이름값과 글줄은 엄연히 다른 갈래의 객체이기 때문에, 반응하는 것 역시 똑같지는 않습니다. #at: 이나 #=, 그리고 #size 등의 지시는 글줄에서와 똑같이 반응하지만, 두 글줄을 합치는 #, 지시와 이름값에서 글줄 토막을 뽑아내는 #copyFrom:to 등의 지시는 조금 다르게 반응했습니다. 즉, #, 지시와 #copyFrom:to의 경우 이름값에 보내진 지시의 결과로 이름값이 나오는 것이 아니라 글줄로 응답했습니다.


이제까지 이름값의 갈래씻줄을 더듬어 올라가서 여러 가지 지시를 내려보았습니다. 그럼 배열의 갈래씻줄은 어떻게 얽혀있을까요? 그리고 배열은 어떤 지시를 알아들을 수 있을까요? 아래의 글토막을 펴 보십시오.

#(1 2 3) class                   Array
Array superclass                 ArrayedCollection
ArrayedCollection superclass     SequenceableCollection


배열의 윗갈래는 배열 모듬(arrayed collection)입니다. 그런데 재미난 것은 이름값과 글줄 역시 이 배열 모듬에 씻줄로 얽혀있다는 것입니다. 바로 위에서 우리는 이름값의 갈래씻줄을 더듬어 올라갔고, 그 중간에 분명히 배열 모듬(arrayed collection) 갈래가 있었습니다. 결국, 이름값, 글줄, 배열, 이 세 갈래의 객체들은 "배열 모듬"인 것입니다.

그림 2-20::이름값, 글줄, 배열의 갈래씻줄 그림 (Stp2-20.gif)


그러면 이름값과 글줄에서 그랬듯이, 배열에도 우리가 자주 사용했던 #size, #=, #copyFrom:to 등의 지지를 사용할 수 있을까요? 자, 아래의 글토막을 펴 봅시다.

#(2 #hello $+) = #(2 #hello $+)  true
#(2 #hello $+) = #(2 #hello $-)  false
#(2 #hello $+) = #(2 #hello)     false
#(2 #hello $+) , #(4 #bye $-)    #(2 #hello $+ 4 #bye $- )
#(2 #hello $+) at: 1             2
#(2 #hello $+) at: 2             #hello
#(2 #hello $+) copyFrom: 1 to: 2         #(2 #hello )
#(2 #hello $+) size              3


신기하지 않습니까? 우리가 글줄과 이름값에 내렸던 지시를 배열도 고스란히 그대로 알아듣습니다. 물론 글줄과 이름값에서처럼 나타난 결과는 달랐지만, 배열에 보낸 지시의 의미를 정확히 이해하고 그대로 실행했습니다. 이것은 바로 글줄, 이름값, 그리고 배열이 "배열 모듬"(arrayed collection)의 아랫갈래들이고, 결과적으로 우리가 사용했던 여러 지시들은 모두 아랫갈래들에 그대로 적용될 수 있음을 보여줍니다.


배열 모듬에 #at: 지시를 보내서 각각의 번호 붙은 성분의 값을 꺼낼 수 있다면 반대로 번호 붙은 성분의 값을 바꿀 수 있는 길수가 있을까요? 네, 당연히 있습니다. 바로 #at:put:이라는 길수입니다. 아래의 글토막을 펴 보십시오.

'hello' at: 1 put: $j            $j
#(2 #hello $+) at: 3 put: $-     $-


결과가 좀 이상하지요? #at:put: 길수는 자신이 방금 번호 붙은 성분에 집어 넣은 객체를 넘깁니다. 즉 지시를 받은 객체(receiver)를 넘기는 것이 아니라 방금 번호 붙은 성분에 들어간 객체를 그대로 내놓습니다. 따라서 지시를 받은 객체가 실제로 변했는지를 알아볼 방법이 없습니다. 물론 그렇다고 전혀 방법이 없는 것은 아닙니다. 아래 글토막을 펴 보십시오.

'hello' at: 1 put: $j; yourself          'jello'
#(2 #hello $+) at: 3 put: $-; yourself    #(2 #hello $- )


위의 바탕글에서 처음 보는 ";"이라는 기호와 "yourself"라는 낱말이 있습니다. 여기에 대해서는 나중에 다시 이야기할 기회가 있을 것입니다. 아무튼 #at:put: 지시를 보내면 해당 객체의 번호 붙은 성분에 새로운 값을 집어넣을 수 있습니다.


그럼 다음과 같이 입력해 보십시오.

#hello at: 1 put: $j     (잘못)


어떻게 되었습니까? 그림 2-18 과 비슷하게 "should not implement"라는 알림글과 함께 발자취 창이 나타났습니다. 꼭 객체 탐색기에서 이름값의 성분을 바꿀 때와 똑같은 일이 일어난 것입니다. 역시 이름값은 배열 모듬이기는 하지만 굳은값이기 때문에 #at:put: 지시를 수행할 수 없었던 것입니다.


바로값

지금까지 우리는 여러 갈래의 객체들을 조사해보았습니다. 각각의 객체는 저마다 나름대로의 갈래에 속해있고, 또한 객체의 갈래에 따라서 겉모습도 달랐습니다. 1, -235 등의 정수, 3.14 같은 소수, '우리 나라'와 같이 생긴 글줄, $a처럼 생긴 글자.... 이들은 모두 한결같이 객체 자체가 가지는 값을 바로 나타낼 수 있습니다. 예컨대, "3"이라고 하면 바로 정수 객체 "3"을 나타내는 것입니다. 이처럼 어떤 표현 방법을 사용해서 객체의 값을 바로 나타낼 수 있는 객체를 "바로값"(literal)이라고 합니다. 어렵습니까?


간단히 생각합시다. "1". 이것은 하나의 객체입니다. 즉 "1"이라고 쓰는 것만으로 바로 "객체"를 나타낼 수 있습니다. 이런 객체를 바로값이라고 부르는 것입니다.


반면 어떤 방법을 사용해서 그 객체를 바로 나타낼 수 없는 경우, 그 객체를 "바로 아닌 값"(non-literal)이라고 합니다. 바로 아닌 값의 대표적인 예로는 분수(fraction)가 있습니다. 분수는 어떤 방법을 사용해서 바로 나타낼 수 없습니다. 물론 (3/4)와 같이 쓰면 분수 "4분의 3"을 나타낼 수는 있지만, 엄밀히 말해서 "(3/4)"는 정수 3과 4, 그리고 겹마디 길표인 #/이 모여서 이루어진 글토막입니다. 이는 일터(workspace)에서 (3/4)를 펴 보면 알 수 있습니다. 3과 4는 보라색으로, '/'은 파란색으로 나타납니다. 즉 (3/4)는 바로 아닌 값인 것입니다.


분수와 같이 간단하게 나타낼 수 있는 경우는 그래도 다행이라고 할 수 있습니다. Smalltalk에 존재하는 객체들 중 대부분은 숫자나 글자 등을 이용해서 직접 나타낼 수 없는 "바로 아닌 값"들입니다. 필자가 뜬금없이 바로값과 바로 아닌 값을 이야기하는 것은, 이것이 Smalltalk의 객체가 가지고 있는 또 하나의 성질이기 때문이고, 실제로 프로그램을 작성하다 보면 이와 같은 성질에 영향을 받는 경우가 있기 때문입니다.


앞서 우리는 배열에 대해서 알아보았는데, 이 때 우리는 무심코 배열 바로값(array literal)을 사용했습니다. 글줄 바로값을 만들 때 ' ' 을 사용한 것과 같이, 배열 바로값을 만들 때에도 #( ) 기호를 사용했습니다. 그런데 여기서 배열에 분수를 넣으려면 어떻게 할까요?

#(1 2 3/4)       #(1 2 3 #/ 4 )
#(1 2 (3/4))     #(1 2 #(3 #/ 4 ) )


어떤 방법을 사용하든지 배열 바로값에 분수를 포함할 수는 없습니다. 왜냐하면 분수는 바로값이 아니기 때문입니다. 즉 바로값을 나타내는데 바로 아닌 값을 사용할 수는 없습니다.


물론 배열에 바로 아닌 값을 넣는 다른 방법이 있습니다. 그러나 여기서는 그것보다는 바로값과 바로 아닌 값이 있고, 상황에 따라서 이 성질이 중요하게 여겨지는 경우가 있다는 것입니다. 보통 바로 아닌 값은 쉽게 글자나 숫자로 나타내기 어렵기 때문에, 보통은 이런 객체에 꼬리표(variable)를 붙여서 쉽게 나타내는 것이 일반적입니다. 꼬리표에 대해서는 바로 다음 마당에서 이야기를 할 수 있을 것입니다.


다른 언어에서의 배열


흔히 많이 쓰이고 있는 C/C++이나 파스칼, 또는 베이식과 같은 언어들은 모두 배열 바로값을 지원하지 않습니다. 이런 언어들은 기본적으로 "배열변수"라고 해서 배열을 변수의 한 종류로 간주하고 있습니다. 그러나 Smalltalk를 비롯한 몇몇 언어들은 배열을 바로값을 이용해서 나타낼 수 있는 능력을 가지고 있습니다. 물론 파스칼의 경우에 집합(set)을 바로값으로 나타낼 수 있지만, 집합보다는 배열이 훨씬 유연하다고 할 수 있습니다.


또한 다른 언어에서의 배열변수는 한결같이 동일한 자료형의 모임이기 때문에 Smalltalk보다 훨씬 제한적인 기능을 가지게 되어 있습니다. Smalltalk 를 공부하다보면 유연한 배열이 얼마나 막강한 힘을 발휘하는지를 알 수 있을 것입니다.


지금까지 우리는 객체의 변화성에 따라 객체를 "굳은값"과 "무른값"으로 나누어보았고, 객체 그 자체를 글자나 숫지 또는 기호 등을 이용해서 직접 나타낼 수 있는가에 따라서 "바로값" 과 "바로 아닌 값" 으로 나누어 보았습니다. 이제까지 우리는 Smalltalk 에서 다루어지는 여러 가지 객체들의 성질을 알아보았습니다. Smalltalk는 객체지향 언어이기 때문에, 조금은 지루했을지 모르지만 객체들의 성질을 파악하는 것은 매우 중요한 작업이었습니다. 이제 다음부터는 보다 더 깊은 객체들의 세계를 탐험하게 될 것입니다. 그 전에 이번 마당에서 알아본 것들을 정리하는 것이 필요할 것이므로, 다음 가름에서 다시 한 번 복습할 수 있는 기회를 만들어볼까 합니다.


정리 및 연습 문제

이 마당에서 우리들은 Smalltalk에 대한 아주 기본적인 것들을 알아보았습니다. 어찌보면 지루했을지도 모르겠지만, Smalltalk 가 가지고 있는 특성들을 제대로 파악해야만 앞으로 공부할 객체지향 프로그래밍의 개념을 쉽게 받아들일 수 있을 것입니다. 필자는 C++ 언어를 공부했기 때문에 감히 단언합니다. 지금까지 우리가 알아본 것들은 결코 C++ 언어의 문법보다 어렵지 않습니다. 놀라실지도 모르지만, 지금까지 우리는 Smalltalk의 문법 중에 70% 를 공부한 것이나 다름이 없습니다. 그만큼 Smalltalk의 문법은 단순합니다. 그러므로 지나치게 문법에만 의존하지 말고, Smalltalk의 살아 있는 객체들이 움직이는 모습을 지켜보면서, 객체지향 프로그래밍의 개념에 익숙해지시기 바랍니다.


요약

이제 이 마당에서 공부한 것을 간단히 요약해 보도록 합시다. 앞서 공부한 것을 되새기면서 한 줄 한 줄 읽어보면 도움이 될 것입니다.

  • Smalltalk 글토막의 겉 모습(문법)과 그 의미
  • 지시를 이루는 요소: 지시(message), 받는이(receiver), 길표(selector), 인자(parameter)
  • 서로 다른 지시의 종류: 외마디(unary) 지시, 겹마디(binary) 지시, 쇠마디(keywored) 지시
  • 둘 이상의 길표가 있는 글토막의 가늠 순서. 우선권 규칙.
  • 갈래(class)와 실체(instance)의 차이
  • 실체와 그 실체를 이루는 구성 요소(부분)
  • 객체 탐색기를 사용하여 객체 안쪽 들여다 보기
  • 정수, 소수, 분수, 글자, 글줄, 이름값, 배열, 논리값, 헛 등이 알아들을 수 있는 지시들.
  • 각종 객체의 속 구조(개체의 성분)
  • 굳은값과 무른값의 다른 점.
  • Smalltalk 글토막의 입력, 수정, 편집 및 실행방법들.


알아두면 좋은 것들

Object 갈래에 딸린 객체들이 알아듣는 지시들
  • anObject class
    • anObject가 속한 갈래를 넘긴다.
  • anObject isKindOf: aClass
    • anObject가 속한 갈래가 aClass 의 갈래 씻줄에 얽혀있는지 조사하여, 얽혀있으면 참을, 그렇지 않으면 거짓을 넘긴다.
  • anObject isNil
    • anObject가 헛(nil)값이면 참을, 그렇지 않으면 거짓을 넘긴다.
  • anObject notNil
    • 위의 경우와 반대이다.
  • anObject inspect
    • anObject 객체를 구성하는 성분들을 탐색한다.


갈래(class)가 알아듣는 지시
  • aClass superclass
    • aClass 갈래의 윗갈래를 넘긴다.


수(Number) 객체가 알아듣는 지시
  • aNumber1 + aNumber2
    • 덧셈
  • aNumber1 - aNumber2
    • 뺄셈
  • aNumber1 * aNumber2
    • 곱셈
  • aNumber1 / aNumber2
    • 나눗셈
  • aNumber1 // aNumber2
    • 정수 나눗셈의 몫을 구한다.
  • aNumber1 \\ aNumber2
    • 정수 나눗셈의 나머지를 구한다.
  • aNumber negated
    • aNumber의 부호를 바꾼다.
  • aNumber numerator
    • aNumber의 분자를 구한다.
  • aNumber denominator
    • aNumber의 분모를 구한다.
  • aNumber reciprocal
    • aNumber의 역수를 구한다.
  • aNumber between: a to: b
    • aNumber 숫자가 a 와 b 사이에 있으면 참을, 그렇지 않으면 거짓을 넘긴다.


정수가 알아들을 수 있는 지시
  • anInteger factorial
    • anInteger 정수의 계승(x!)을 구한다.
  • anInteger asCharacter
    • anInteger를 글자 번호로 하는 글자를 넘긴다.
  • anInteger asFloat
    • anInteger 정수를 소수(floating-point number)로 바꾼다.


소수(float)가 알아들을 수 있는 지시
  • aFloat rounded
    • 소수 aFloat을 반올림한다.
  • aFloat truncated
    • 소수 aFloat의 소수점 이하를 잘라버린다.


논리값(Boolean)이 알아들을 수 있는 지시
  • aBoolean | aBoolean
    • 두 논리값 중에 어느 하나라도 참이면 참을 넘긴다.
  • aBoolean & aBoolean
    • 두 논리값이 모두 참일 때 참을, 그렇지 않으면 거짓을 넘긴다.
  • aBoolean not
    • aBoolean이 참이면 거짓을, 거짓이면 참을 넘긴다.
  • aBoolean and: [aBlock]
    • aBoolean이 참이면 aBlock을 실행하고, aBlock의 값이 참인 경우에는 참을, 그렇지 않으면 거짓을 넘긴다. 만약 aBoolean의 값이 거짓이면 aBlock이 실행되지 않고 거짓을 넘긴다.(#&의 짧은 경로 계산)
  • aBoolean or: [aBlock]
    • aBoolean이 거짓이면 aBlock을 실행하고, aBlock의 값이 거짓인 경우에는 거짓을, 그렇지 않으면 참을 넘긴다. 만일 aBoolean의 값이 참이면 aBlock이 실행되지 않고 참을 넘긴다.(#|의 짧은 경로 계산)


글줄, 이름값, 배열이 알아듣는 지시
  • container size
    • 해당 객체(container)가 몇 개의 원소로 되어 있는지를 구한다.
  • container , anotherContainer
    • 두 객체를 합한다.
  • container at: anInteger
    • container 객체의 anInteger 번째의 원소를 꺼낸다.
  • container at: anInteger put: anObject
    • container 객체의 anInteger 번째 성분에 anObject 객체를 넣는다.
  • container copyFrom: a to: b
    • container 객체의 번호붙은 성분 중 a에서 b까지의 부분 원소를 뽑아낸다.


글줄이 알아들을 수 있는 지시
  • aCharacter isUppercase
    • 글자가 대문자이면 참을 넘긴다.
  • aCharacter isLowercase
    • 글자가 소문자이면 참을 넘긴다.
  • aCharacter isLetter
    • 글자가 영문자이면 참을 넘긴다.
  • aCharacter isDigit
    • 글자가 숫자(0-9)이면 참을 넘긴다.
  • aCharacter isVowel
    • 글자가 모음(a,e,I,o,u)이면 참을 넘긴다.
  • aCharacter asciiValue
    • 글자가 가진 글자 번호를 넘긴다.


우선 순위 규칙

하나의 글토막에 여러 종류의 지시가 섞여 있을 경우는 왼쪽으로부터 오른쪽으로 가늠되며, 다음의 순서를 가지고 가늠됩니다.

  • 외마디 지시
  • 겹마디 지시
  • 쇠마디 지시


Smalltalk에서의 풀이글

풀이글은 " "안에 쓰고, Smalltalk는 내용을 무시한다.


연습 문제

  1. 일터에 자유로게 글을 입력한 다음 "Test.st"라는 파일에 저장하고 일터를 닫은 뒤에, 새로운 일터를 열고 방금 저장했던 파일을 열어 봅시다.
  2. Smalltalk에는 날짜를 나타내는 "Date"갈래가 있습니다. 이 Date 갈래의 씻줄을 더듬어 봅시다. 그리고
    Date today
    
    와 같은 문장으로, 즉 Date 갈래에 #today 지시를 보내면 오늘 날짜가 담긴 객체를 얻어낼 수 있습니다. 이 객체들이 비교될 수 있는지 알아보고, 마지막으로 글줄과 날짜 객체 역시 서로 같은지 다른지를 비교해 봅시다.
  3. 작은 정수(SmallInteger) 중에서 제일 큰 값은 얼마일까요?
  4. 이글에 큰 따옴표(")를 사용하려면 어떻게 해야 할까요?
  5. 어떤 수를 0으로 나눌 수 있을까요? Smalltalk에서 직접 실행해 봅시다. 만약 0으로 나눌 수 없다면 Smalltalk는 어떤 알림글을 뿌려줍니까?
  6. 큰 정수(LargeInteger)를 탐색해 봅시다. 이 객체가 핫객체인지 홀객체 인지를 알아보고, 만약 핫객체라면 번호 붙은 성분을 가졌는지, 이름 붙은 성분을 가졌는지 조사해 봅시다.
  7. 아래 진리표를 완성해 보십시오.
& 거짓
거짓
|
거짓


마무리

이제 두 번째 마당이 끝났습니다. 여기까지 온 여러분은 이제 Smalltalk 가 어떻게 객체를 처리하고, 또한 객체에게 어떻게 지시를 보내며, 또한 객체가 응답하는 것을 어떻게 처리할 수 있는지에 대해서 공부했습니다. 그리고 또한 Smalltalk 에는 많은 종류의 객체가 존재한다는 것도 알 수 있었습니다. 결국 Smalltalk 는 이러한 객체들의 모임으로 이루어진 시스템이기에, Smalltalk를 이해하려면 먼저 이러한 객체들의 특성을 파악하는 것이 중요하다는 말은 여러 번 했습니다. Smalltalk 의 모든 객체는 살아있으며, 자신이 어떤 일을 해야할지도 알고 있다는 말은, 이제는 지겨울 때도 되었을 만큼 많이 언급한 것입니다.


두 번째 마당까지 오게 된 여러분은 이제 Smalltalk 의 기본적인 환경인 일터(workspace)와 객체 탐색기(object inspector)에 대해서 공부했습니다. 이 두 가지의 도구는 앞으로 여러분이 Smalltalk의 객체를 다룰 때 늘 옆에 두고 있어야 하는 친구일 것입니다.


이제 다음 마당에서는 여러분들에게 보다 재미있고 또한 유용한 객체들에 대해서 알아보려고 합니다. 또한 객체에 이름을 붙여놓고 그 이름을 통해서 객체를 여러 가지 방법으로 제어할 수 있는 방법도 알아볼 것입니다. 지금까지의 마당이 주로 단순한 글자와 글줄, 그리고 숫자 정도만 다루어본 것에 비해 이제 다음 마당부터는 좀 더 복잡하면서도 재미있는 객체들에 대해서도 알아볼 것입니다. 이로써 여러분은 서서히 객체지향 패러다임을 몸으로 느껴보게 될 것입니다. 기대하셔도 좋습니다. :)


Notes