SmalltalkObjectsandDesign:Chapter 03

From 흡혈양파의 번역工房
Jump to: navigation, search
제 3 장 스몰토크 소개

스몰토크 소개

스몰토크의 언어는 아주 작다. 그것의 대부분을 이번 장에서 학습할 것이다. 그리고 이번 장에서는 앞의 두 장에서 이미 살펴본 내용을 검토하고자 한다. 그렇다고 스몰토크 자체가 아주 작은 것은 아니다. 일반적인 스몰토크 시스템은 본래 수 천 개의 클래스로 이루어진 라이브러리를 포함하고 있다. 그리고 스몰토크에서는 클래스 없이는 아무것도 할 수 없으며, 후에 살펴보겠지만 심지어 조건문(conditional) 혹은 루프(loop)처럼 일반적인 것도 실행할 수 없다. 언어의 학습은 단시간에 가능하지만 "스몰토크"를 학습하는 것은 무거운 도전과제가 될 수 있다. 효과적인 프로그래머가 되기 위해서는 많은 클래스를 (절대로 모두 학습해야 하는 것은 아니다) 학습해야 할 것이다. 또한 그러한 클래스의 현명한 사용은 훌륭한 객체 지향 디자인으로 이어진다. 이를 숙달하기까지는 수개월의 실습을 요한다-자신의 손가락을 이용해 직접 실행하고 시스템을 뒤져보며, 자신만의 애플리케이션을 작성하고 이것저것 수정하는 과정이다. 약간의 호기심과 대담함은 자신에게 도움이 된다.


기본 객체

몇 가지 기본(elementary) 스몰토크 객체들은 고유의 특수 표기법을 정할만한 가치가 있다. 여기 몇 가지 예가 있다:

예제 인스턴스 인스턴스의 클래스
37 Integer (실제 서브클래스 SmallInteger)
'To be or not to be' String
2.71828 Float
$p (문자 p) Character
true Boolean (실제 서브클래스 True)
false Boolean (실제 서브클래스 False)


이러한 표기법의 부산물 중 하나는 숫자(number) 객체를 생성하기 위해 전형적(customary) 메시지인 Float new 또는 Integer new를 issue하지 않는다는 점이다. 대신 2.71828 표현식 자체만으로 부동소수점 (floating point) 객체로의 참조를 생성한다. 사실 Float new 또는 Integer new를 실행하거나 표시하면 오류 또는 walkback 창이 뜬다. 스몰토크는 숫자형 클래스에 대한 new 메시지를 비활성화한다.


메시지와 메시지의 우선순위

스몰토크는 정확히 세 가지 유형의 메시지를 갖고 있다: 단항, 이항, 키워드가 그것이다. 키워드 메시지는 콜론을 포함하는 메시지다. 아래 메시지를 예로 들어보자:

HomeBudget spend: 229 on: 'VCR'

이는 선택자가 spend:on: 인 키워드 메시지다.


이러한 선택자는 서로 충돌된 두 개의 키워드로 구성됨을 주목하라. 각각이 argument를 기대한다. 따라서 이 메시지는 두 개의 argument 객체, 이름하야 229과 'VCR'를 포함한다. 키워드 메시지는 원하는 수만큼 키워드를 (예: 콜론) 가질 수 있으며, 각 콜론 뒤에는 하나의 argument를 가져야 한다.

이제 위의 메시지를 내부적으로 파싱(parse)하는 또 다른 방법은 다음과 같으며:

(HomeBudget spend: 229) on: 'VCR'


두 개 메시지의 총계에 대해 하나의 메시지 다음에 나머지 메시지가 따라오는 형태로 되어 있다. 이는 대안적 방법이지만 스몰토크가 작동하는 방식은 아니다. 원본 표현식은 괄호가 포함되어 있지 않았기 때문에 스몰토크는 두 개의 argument를 얹은 하나의 메시지로 해석한다. 대신 두 개의 구분된 메시지를 표시하고 싶다면 명시적으로 괄호를 사용해야 한다.

이항 메시지는 제일 알아채기 쉽다. + - * / <<= > >=과 같은 특수 기호로 표기된다.

17 <= 14


위는 이항 메시지다. 그 선택자는 <=이며 그것이 리턴하는 객체는 false 객체이다.

콤마는 이항 메시지의 놀라운 예제이다. 콤마는 두 개의 String 객체를 결합(concatenate)하는 데에 가장 자주 사용된다:

'Let them eat' , 'cake.'


스몰토크에게 결과를 표시하길 요청하면 스몰토크는 다음을 표시한다: 'Let them eat cake.' 콤마와 비교식과 산술 기호가 유일한 이항 선택자는 아니다. + - * / \~ > < = @ % l & ? , 기호 중 하나 또는 두 개를 선택자로 이용하여 이항 메시지를 정의할 수 있다. 꼭 그래야 하는 것은 아니다. 선택자 @\도 전적으로 유효하지만 양호하게 사용되는 모습을 상상하기가 힘들다.

그 외 모든 메시지는-선택자가 특수 문자도 아니고 콜론도 없는-단항 메시지일 것이다. 단항 메시지의 선택자는 하나의 단어로 구성되는데, 예를 들자면:

'smart' reversed


위의 메시지에서 선택자는 reversed이며;

4 factorial


위에서 선택자는 factorial이다.

한 가지 이상의 메시지 유형으로 구성된 복잡한 표현식을 마주하면, 스몰토크의 우선순위 규칙은 단항 다음으로 이항, 그 다음으로 키워드 순이다. 예를 들어,

12 between: 7 and: 9 + 2


위의 표현식은 키워드 메시지와 이항 메시지를 포함한다. 이항 메시지가 키워드보다 우선순위가 높으므로 9+2가 먼저 실행되어 11의 결과를 낳는다. 다음으로 12 between: 7 and: 11, 즉 키워드 메시지가 실행되면서 12에게 "7과 11 사이의 값입니까?"라고 질문하면 12는 최종적으로 false 객체로 응답한다.

표현식에 괄호를 삽입하면 무슨 일이 일어날까?

(12 between: 7 and: 9) + 2


괄호는 우선순위를 바꾼다; 이번 경우 오류창(walkback)을 야기할 것이다. 이유는 다음과 같다. 괄호 안의 표현식이 먼저 실행되어 false 객체로 이어진다. 그리고 false + 2가 실행되어 false 객체에게 2를 더하도록 요청한다. 스몰토크의 false 객체는 덧셈을 이해하지 못하기 때문에 walkback 창이 뜨면서 오류를 알린다. 다른 언어에서와 마찬가지로 괄호의 사용은 큰 변화를 야기한다.


위험: 기본 스몰토크 이해하기

처음으로 단순한 위험은 스몰토크는 대, 소문자에 민감하므로, Whale을 whale로 써놓고 코드가 제대로 작동하길 기대할 수 없다. 스몰토크의 대문자 규칙은 변수와 관련된 절에서 (39 페이지) 다룰 것이다.

또 다른 위험으로, 스몰토크 지정문(assignment)은 메시지가 아니라는 점을 들 수 있겠다. 이 지정문은 '좌측에서 우측으로' 규칙에 예외가 된다. 스몰토크는 :=의 우측을 먼저 처리한 후 결과 객체를 좌측의 변수에 놓는다. 예를 들어,

X :=12 between: 7 and: 9 + 2


위는 X가 12 객체를 참조한다는 의미가 아니다. 우선 지정문의 우측에 있는 전체 표현식이 실행되어 false 객체가 결과가 되는데, 지정문이 발생한 다음에만 그러하다. 결국 X는 정수 12가 아니라 false 객체를 참조한다.

더 서서히 나타나는 위험으로, Character, String, Symbol, Array 의 인스턴스들 간 차이와 관련된다. 그러나 그 차이를 논하기 전에 먼저 유사점을 강조하고 싶다. 19 또는 2.71828과 같은 숫자를 비롯해 이러한 객체들은 종종 리터럴(literals)이라 부른다. IBM Smalltalk에서 리터럴은 변경이 불가하다-읽기만 가능한 객체로서 수정할 수 없다. (다른 dialects에서 리터럴 문자열과 리터럴 배열은 변경 가능하다.) 리터럴 객체를 나타내는 방법은 다음과 같다:

Smalltalk 의미
192.71828 숫자 (aSmallInteger와 aFloat)
$b 단일 문자 b
'rosebud' aString
#rosebud aSymbol (아래 설명)
# (5 'rosebud' 7) anArray

(aString에서와 같이 단어들을 모두 모아서 대문자를 하나만 사용하여 경계를 표시하는 방법이 스몰토크의 관습이다.)


Symbol의 인스턴스는 숫자 기호(#)로 시작하는 1 또는 그 이상의 문자에 대한 시퀀스이다. 기호와 문자열 간 눈에 보이는 차이를 제외하고 엄청난 차이가 한 가지 있다: 두 개의 문자열이 동일한 문자 시퀀스를 가질 수는 있지만, 두 개의 기호가 동일한 문자 시퀀스를 갖는 것은 불가능하다. 기호 #rosebud가 한 번 이상 발생한다 하더라도 스몰토크는 모든 발생을(occurrence) 하나의 동일한 기반 객체를 참조하는 것으로 구성한다. 그 부산물로 기호를 복사하면…동일한 기호가 생긴다! 이러한 기호의 행위는 제 6장에서 다룰 객체 정체성 문제에서 중요한 역할을 한다.

String의 인스턴스는 하나의 인용 부호로 경계가 정해진 0 또는 그 이상의 문자에 대한 시퀀스이다. "에는 문자가 없고 'b'에는 하나의 문자가 있지만 둘 다 String의 정당한 인스턴스를 나타낸다는 사실을 주목하라. 그리고 'b' 문자열과 $b 문자를 혼동하지 말라; 완전히 다른 클래스들의 인스턴스다. 다른 많은 언어에서와 마찬가지로 문자열 내 인용부호는 이중 부호로 해야 한다: 'Alices Restaurant'. 다시 말하지만, 스몰토크 시스템은 'rosebud' 문자열의 많은 인스턴스를 포함할 수 있지만 기호 #rosebud의 인스턴스는 최대 하나만 포함할 수 있다. 스몰토크에서는 Symbol이 String의 서브클래스가 될 수도 있다. 따라서 기호는 "동일함"의 의미나 정체성이 다른 특별한 유형의 문자열로 생각할 수 있겠다.

문자 다음이 아니라 # 다음에 괄호를 넣으면 더 이상 기호를 얻지 않는다는 사실을 주목하라. 당신은 기호 대신 배열을 얻게 된다. 배열 #에는 (5 'rosebud' 7) 세 개의 요소가 있다: 첫 번째는 정수 5, 다음으로 문자열 'rosebud', 마지막으로 정수 7이다. 리터럴을 리터럴 배열 내에 중첩(nest)할 수 있다. 예로, #(5 #rosebud 7)는 세 개의 요소를 갖는데, 그 중 두 번째는 기호이며, #(5 #(2 11 13) 7) 또한 세 개의 요소를 갖는데 두 번째가 배열이다. (마지막 두 예제에서 이상하게 보이겠지만 내부에 포함된 #는 IBM Smalltalk에서 선택적이다.) 마지막으로 인용 부호는 주석의 범위를 정하는데, 스몰토크에서는 무시된다. 따라서 "rosebud"는 주석이다. 코드에서 흰색 공간이 발생하는 곳에선 자유롭게 주석을 이용할 수 있다.


예제

아래 표현식 각각에서 어떤 값이 리턴될 것으로 예상되는가?

3  5 * 2
Integer superclass
#(me you they) at: 2


답은 -4, Number, #you가 된다. 첫 번째 표현식은 두 개의 연속된 이항 메시지로 구성되어 좌측부터 우측으로 처리된다. 두 번째는 Integer로 전송되는 단항 메시지로, 이 클래스에게 그것의 슈퍼클래스를 독자에게 알려줄 것을 요청한다. 마지막은 단일 argument 2를 둔 키워드 메시지로, array 객체에게 두 번째 요소로 응답할 것을 요청한다; 두 번째 요소는 바로 기호 #you이다.

각 메시지의 효과는 어떻게 예상되는가? 리턴값은 생각하지 말고 그저 효과만 생각하라.

#zero at: 1 put: $h
'zero' at: 1 put: $h
'zero' copy at: 1 put: $h
#zero copy at: 1 put: $h


첫 두 개의 메시지는 리터럴의 변경이 불가하기 때문에 실패할 (!) 것이다. 세 번째는 잘 작동한다; 리터럴 문자열을 복사 시 리터럴이 아닌 새로운 문자열이 생성된다. 이러한 복사본의 첫 번째 문자는 대체되고, 복사된 문자열은 'hero'가 된다. 마지막 메시지는 다시 실패한다. 복사본에 원본 기호와 동일한 문자의 시퀀스가 있는데, 기호의 경우 복사본이 원본과 동일하게 변경 불가한 상태여야하기 때문이다.


연습: 가설적 메서드

아래 코드는 replaceLastBy:라는 이름의 완전하고 가설적인 스몰토크의 메서드이다. 이는 언어의 여러 요소들을 소개한다. 잘 살펴보고 아래의 질문에 답해보라.

❏ 메서드는 어떤 일을 하는가?


❏ 작동하려면 어떤 클래스가 되어야 하는가?


코드는 다음과 같다:

 replaceLastBy: anObject
      last
      last := self size.
      Self at: last put: anObject.


처음으로 보는 것들에 대해 해설이 필요할 것이다:

  • 첫 행은 선택자-키워드 replaceLastBy:m와 메서드가 argument에 사용하게 될 더미(dummy) 이름만 포함한다. 필자는 이 이름으로 건강하면서 전통적인 스몰토크 코딩 스타일을 나타내는 anObject를 선택했는데, 이론상으로는 어떤 이름도 똑같이 이용 가능하다.
  • 다음 행은 (주석 다음) 수직 막대들 사이에 로컬 변수(local variable)를 선언한다; 그 이름은 last이지만 원하는 대로 명명할 수 있다. 객체를 참조하기 위해 해당 로컬 변수를 메서드에서 내내 사용할 수 있다. 막대들 사이에 원하는 수만큼 선언 가능하며 각각은 공백으로 구분한다.
  • 마지막 두 행에 나타나는 self를 이해하기 위해서는 동형적으로(anthropomorphically) 사고하는 것이 도움이 된다. I 를 메서드가 실행되는 객체라고 상상해보자. self는 me를 참조하는 특별한 스몰토크 변수이다. (하지만 당신은 I 가 누구인지 아직 알 수 없는데, 그 이유는 그것이 이 연습의 두 번째 질문에 대한 답이기 때문이다.)
  • 마지막으로, 행의 마지막에 간격(period)을 주목하라; 구분된 Smalltalk 문(statement)들이다. 간격은 구분자(separator)이므로 마지막 간격은 선택적이다.


위의 두 질문을 공략하기 전에 마지막 힌트로, 구체적인 상황을 상상해볼 것을 권한다. 일부 객체는 그 내부에 여러 개의 요소를 갖고 있으며 replaceLastBy:$e 메시지를 수신한다고 상상해보자.


해결책과 논의

메서드는 어떤 일을 하는가? I 가 가령 4개의 요소를 갖고 있다고 가정해보자. 다시 말해, 내(I)가 일종의 컬렉션이라고 생각해보자. 그러면 self size는 내 크기를 4라고 계산한다. 그리고 self at: 4 put: anObject는 나의 네 번째 요소가 무엇이든 그것을 anObject로 대체한다. 예를 들어, 내(I)가 b, l, u, r 문자를 포함하는 문자열이라고 가정하면 Me 변수는 나(me)를 참조한다. 메시지(또는 전보)를 수신하면:

Me replaceLastBy: $e


나는 나의 마지막 요소를 문자 e로 대체하라는 요청을 받는다. 따라서 메서드는 나를 b, l, u, e 문자로 된 문자열로 변형시킨다.. 일반적으로는 Me 객체가 무엇을 참조하든 메서드는 객체의 마지막 요소를 대체하려고 시도할 것이다.

두 번째 질문에 대해, Me에 가장 바람직한 클래스는 무엇인가? 분명한 지원자는 String인데 'blur'가 문자열이기 때문이다. 다른 건 어떨까? 어쩌면 Array, 또는 인스턴스가 인덱스된 정렬(indexed ordering)을 갖고 있는 클래스일지도 모른다. String과 Array가 가장 명백한 두 개지만 나머지는 스몰토크를 더 학습하면서 만날 것이다.

메서드에 자주 나타나는 또 다른 언어 요소로 탈자 기호, ^를 들 수 있다. ^xyz는 메서드가 그것을 호출한 대상에게 xyz를 리턴하고 실행을 중지해야 함을 의미한다. ^의 우측에 복잡한 표현식이 있을 경우, 전체 표현식이 실행된 다음에 메서드는 생성된 표현식이 무엇이든 그것을 호출자(invoker)에게 리턴하고 실행을 중단한다. 따라서 아래와 메서드에서 같은 문(statement)은:

^5 + 6 * 2


메서드를 종료하고, 호출자에게 22를 리턴할 것이다. 또는:

^self


위의 경우, 메서드를 종료하고, 호출자에게 "me"를 리턴할 것이다.

이 예제와 같은 메서드에 탈자 기호가 포함되지 않은 경우에도 스몰토크는 여전히 메서드가 무언가를 리턴해야 한다고 우긴다-스몰토크 메서드는 항상 어떤 객체를 리턴한다. 이 기본 리턴 객체는 항상 self이다. 따라서 메서드는 아래와 같이 작성될 수 있을 것이다:


    replaceLastBy: anObject
     	last
     	last := self size.
     	Self at: last put: anObject.
	^self


변수의 종류

변수는 어떤 철자로든 쓸 수 있다. 변수의 첫 번째 글자가 대문자일지, 소문자일지는 일부 스몰토크에서 (IBM Smalltalk와 VisualWorks) 선호도의 문제지만 다른 스몰토크에서는 (Smalltalk/V) 엄격하게 강요된다. IBM Smalltalk는 이 점과 관련해 상당한 자유범위를 제공하지만 필자는 공통 규칙을 (Smalltalk/V 규칙과 동일한) 사용하길 권한다. 그러면 자신의 스몰토크 코드가 다른 스몰토크 코드와 비슷해져 결국은 상호 가독성을 향상시킬 것이다. 규칙은 다음과 같다:


하나의 객체에만 보이고 소문자로 된 변수 anObject 또는 last로 시작하라. 이러한 변수의 가장 공통된 종류는 다음과 같다:

  • anObject와 같은 형식적 인자(formal arguments). 메서드 젤 위에 선택자와 함께 선언된다.
  • last와 같은 로컬 또는 임시 변수. 수직 막대 내에서 선언된다.
  • numerator와 같은 인스턴스 변수. 그것의 Fraction 클래스와 함께 선언된다 (23 페이지).


또 다른 종류의 변수는 55 페이지에 블록을 논할 때 살펴보겠다. 이러한 변수들은 하나의 객체에만 표시되지만 첫 번째 두 종류는 (anObject와 last) 심지어 가시성이 덜하다; 그들을 정의하는 단일 메서드 내에서만 사용 가능하다. 반면, numerator와 같은 인스턴스 변수들은 객체의 여느 메서드든 사용 가능하다.

문자가 대문자로 된 다수의 객체들 중 공유 가능한 변수부터 시작하라. 대문자로 된 변수에서 가장 일반적인 유형이 전역 변수이다. 전역 변수는 전반적으로 눈에 다 보인다; 따라서 스몰토크 내 어떤 객체든 이를 보고 사용할 수 있다. 당신은 이미 한 가지 종류의 전역 변수에-클래스-익숙할 것이다 (분수와 whale에 대한 실제 클래스를 가리키는). 또 흔히 사용되는 전역 변수는 당신이 개발한 것들로 구성되어, 후에 객체로 참조할 수 있다. 그 예로 X := Whale new 표현식에서 전역 변수 X를 들 수 있겠다. X는 whale 인스턴스를 참조할 수 있는 핸들을 제공한다. (권장: 숙련된 객체 프로그래머들은 전역 변수의 사용을 삼가하는데, 전역 변수들은 캡슐화에 상반되는 것이기 때문이다. 당신의 목표는 가능한 한 이를 숨기는 것인데, 전역 변수는 정반대의 효과를 보인다. 하지만 전역 변수의 사용을 전적으로 피할 수는 없다: 스몰토크에서 클래스 없이는 할 수 있는 일이 많기 때문이다.)

공유 가능한 (대문자) 변수의 또 다른 종류로 클래스 변수가 있는데, 이는 별로 전역적이지 않다. 이 변수들은 한 클래스의 인스턴스들과 서브클래스들이 모두 공유할 수 있는 변수이다. 예를 들어, 그래픽 아이콘(graphical icon)은 그 클래스의 모든 인스턴스가 아이콘의 private copy(사본)를 각자 가질 필요는 없으므로 클래스 변수가 될 수 있다. 혹은 여러 텍스트 창에서 텍스트를 복사해 붙이길 원할 경우, 모든 텍스트 창 인스턴스가 TextBuffer를 공유하면 편리하겠다. 모든 객체는 아니지만 하나 이상의 객체에 표시되는(visible) 클래스 변수들은 단일 객체가 즐기는 변수들과 전역 변수들의 중간에 위치한다.

또 다른 공유 가능한 (대문자) 변수는 클래스 인스턴스 변수로 알려진다. 클래스 인스턴스 변수는 solitaires를 구현 시 편리하다 (230 페이지); 추후 설명하겠다.

요약: 산업에서 스몰토크 코드의 우월성을 지키기 위해서는 대문자로 된 전역, 클래스, 클래스 인스턴스 변수부터 시작하고, 그 다음으로 소문자로 된 하나의 객체에만 해당하는 변수 어떤 것이든 시작하면 된다.


위험: 변수≠객체

변수가 객체가 아닌 것은 당연한 사실이다 (20 페이지). 오히려 변수는 객체를 "가리키거나" 객체를 참조한다고 보는 편이 옳다. 언제는 변수 X가 정수 92를 참조하기도 하고, 또 다른 때는 그것을 재할당하여 X가 'Call me Ishmael' 문자열을 참조하기도 한다. 스몰토크는 X가 어떤 타입의 객체든 참조하도록 허용한다. 그래서 "스몰토크는 타입이 정해지지 않은(untyped) 언어이다,"라는 말을 듣게 되는 것이다. (제 17장까지는 "타입"을 "클래스"를 나타내는 대안적 용어로 생각하는 편이 안전하다.)

변수의 타입은 그때그때 다를 수 있지만 객체의 타입이 모호한 경우는 절대로 없다. 92는 항상 Integer 클래스의 (사실은 31-bit 양과 음의 정수로 구성된 Integer 의 서브클래스 SmallInteger) 인스턴스가 될 것이고, 'To be or not to be'는 항상 String 클래스의 인스턴스가 된다. 따라서 "스몰토크에서 변수는 타입이 정해지지 않지만 객체는 강하게 타입화(strongly typed)된다"고 말할 수 있는 것이다. 여기에 유일한 예외가 있는데, 사실상 객체의 클래스를 변경할 수 있는 강력한 스몰토크 메서드 become: 가 바로 그것이다. (프록시와 고스트는 제 18장 논의를 참고한다.)


클래스는 객체다

"모든 것은 객체이다"는 정신에 따라 스몰토크 클래스는 (팩토리는) 자체만으로 특별한 유형의 객체가 된다. 다른 객체와 마찬가지로 클래스들 또한 메시지를 수신할 수 있으며, new는 가장 가장 최근의 메시지가 된다. 이는 대단한 일이다-다른 대부분의 객체 지향 언어에서는 분명 클래스는 객체가 아니다. 그래서 다음과 같은 표현을 듣는 것이다: "스몰토크 클래스는 일급(first-class)의 객체이다."

클래스에 적용되는 메서드를 클래스 메서드라고 부르며, 인스턴스에 적용되는 메서드는 인스턴스 메서드라고 부른다. 두 종류의 메시지 모두 아래 표현식에 나타난다:

Whale new talk


new를 수신하는 객체는 Whale 클래스다; 따라서 new는 클래스 메서드이다. talk 메시지를 수신하는 객체는 팩토리 문에서 나서는 whale 인스턴스이다; 따라서 talk는 인스턴스 메서드다.

스몰토크 브라우저는 인스턴스 메서드와 클래스 메서드를 구별해서 보여준다. 아래 브라우저는:

Chapter 03 01.png


상단 우측에 세 개의 인스턴스 메서드를 표시한다. 창 중앙 근처의 푸시 버튼에 인스턴스라고 적혀 있으므로 인스턴스 메서드인 것을 알 수 있다. 이 버튼을 토글하면 아래가 표시된다:

Chapter 03 02.png


Semaphore 클래스는 new라는 이름으로 된 하나의 클래스 메서드만 정의한다. 이는 일반적인 상황을 반영한다: 스몰토크는 클래스 메서드보다 인스턴스 메서드를 더 많이 갖는다. 계산에 유용한 작업을 하는 대부분은 인스턴스라는 사실을 감안하면 놀라운 일도 아니다.


제어 흐름

프로그래밍에 관해 일찍이 배우는 사실들 중 하나는 프로그램은 조건적으로 나뉘고 (if-then) 루핑(loop)할 수 있어야 할뿐 아니라 시퀀스 문(sequence statement)를 차례로 나눌 수 있어야 한다. 아직도 조건문(conditionals) 또는 루프(loops)에 대한 스몰토크의 언어 문(language statement)이 없다. 스몰토크는 이를 유일하게 아는 방법, 즉 메시지를 통해 이를 성취한다. 먼저 우리는 하나의 추가적 언어 요소가 필요하다. 블록은 객체의 특별한 유형으로, 브래킷[…]으로 범위가 지정된다. 블록은 이름이 없는 코드 무더기와 같이 행동한다. 간단한 블록을 예로 들면 다음과 같다:

[Whale new talk]


블록은 객체이므로 객체처럼 취급하여 먼저 할당하고, argument로서 전달한 후, 그것으로 메시지를 전송한다. 예를 들어, 이것은 실행을 통해 변수로 할당할 수 있다:

MyBlock := [whale new talk].


스몰토크에서 의무적이지만 화면에는 어떤 것도 나타나지 않는다. 이는 필자가 블록을 MyBlock으로 할당만 했기 때문이다. 블록을 실행하기 위해서는 어떤 일도 하지 않았다. 이를 실행하려면 블록에 value라는 특수 메시지를 전송한다:

MyBlock value


이제 스몰토크가 마침내 'I spout and sing! '이라고 응답한다. 블록은 사실상 코드의 실행을 적절한 때까지 지연하는 데에 사용하는 방법이다. 이제 조건문과 루프 차례다. 조건부 갈림(Conditional branch)의 예를 들자면 아래와 같다.

MyValue < 17
	ifTrue: [Whale new talk].


이항 메시지(<)가 키워드 메시지보다(ifTrue) 앞서므로, 스몰토크는 먼저 MyValue가 17보다 적은지를 결정하여 true 객체 또는 false 객체를 야기한다. 답이 true 객체인 경우 ifTrue: 메서드가 블록을 평가한다. 이후 control은 잇따른 문(statement)들 중 아무데나 전달한다. 만일 답이 false 객체라면 ifTrue: 메서드가 아무 일도 하지 않는다; 코드의 블록을 평가하려고 애쓰지 않으며, control은 여전히 잇따른 문(statement)들 중 아무데나 전달한다. 그리고 당신은 원하는 결과를 얻는다: MyValue<17의 결과에 따라 블록이 실행하거나 실행하지 않는다. 루핑(looping)에는 몇 가지 방법이 있다. 그러한 메서드를 호출하는 간단한 메시지를 예로 들어보겠다:

6 timesRepeat: [K := K + 1]


이 메시지는 코드 블록의 6회 반복(repetition)을 야기한다. 수신하는 객체는 낮은 정수 6이라는 사실을 주목하라. 다시 말해, Integer 클래스는 풍부한 행위를 가져서 그 인스턴스들 모두가 루프를 제어할 수 있다는 말이다; 모두들 키워드 메시지 timesRepeat:을 이해한다. 이 예제는 스몰토크 객체 모델이 얼마나 극단적인지 보여준다. 정수와 같은 무해한 객체에게 풍부한 행위를 제공하는 데에 방해하는 요소는 어떤 것도 없다.


논평: 메타클래스

이번 절에서는 제 20장에서 상세히 다루는 고급 주제를 맛보기로 소개하고자 한다. 클래스를 일급 객체로 인정한 결과 중 하나는 클래스 자체가 다른 일부 클래스의 인스턴스여야 한다는 점이다. 사실상 팩토리는 일종의 고급 팩토리에서 비롯되어야 한다. 따라서 Whale과 같은 모든 클래스는 자체가 어떤 클래스, 즉 메타클래스라고 불리는 것의 인스턴스다. 게다가 Whale 클래스는 그것의 메타클래스의 유일한 인스턴스가 된다. 사실상 모든 클래스는 자신의 메타클래스의 유일한 인스턴스이다.

그렇다면 만일 모든 클래스에 관련된 메타클래스가 있고 그 클래스가 그 메타클래스의 유일한 인스턴스라면, 시스템 주위로 실행되는 클래스 같은(class-like) 객체들의 수가 두 배가 된다. 최악의 상황, 즉 메타-메타클래스 식으로 영원히 계속될까봐 두려워하는 사람도 있을 테지만 다행히 이는 걱정하지 않아도 된다. 다음 단계는 훨씬 간단하다. 모든 메타클래스는 하나의 동일한 클래스, 그 이름인즉슨 Metaclass의 인스턴스들이다. 클래스 같은(class-like) 객체들이 폭발(explosion)하면 이 하나의 클래스로 갑자기 중단한다. 이를 모두 세어 낼 수도 있다. 시스템에 2000개의 일반 클래스가 있다면-그러니까 1999개에 Metaclass를 하나 더해서-메타클래스 또한 2000개를 가진다. 그리고 2000개의 메타클래스가 모두 Metaclass의 인스턴스들이다. 그럼 전체적으로 4000개의 클래스 같은(class-like) 객체들이 된다.

4000개는 클래스 브라우저가 당신에게 인식시키는 데에 필요로 하는 수보다 2000개가 많다. 클래스 브라우저는 실용적인 도구로, 2000개의 메타클래스를 숨기도록 고안되었다. 어떻게 숨길까? 앞에서 보았듯 "클래스 메서드"를 볼 수 있도록 해주는 편리한 토글 기능을 통해서일까? Whale의 클래스 메서드라 일컬어지는 것은 사실상 Whale의 메타클래스의 인스턴스 메서드들이다! 이런 교묘한 속임수를 통해 스몰토크 브라우저는 Whale의 메타클래스를 숨기고, 그 메서드들을 우리에게 "클래스 메서드"라 불리는 메서드의 인위적 종자(artificial breed)로서 표시한다.

메타클래스에 관해 필자가 설명한 내용을 모른다 하더라도 스몰토크에서 오랜 시간동안 프로그래밍을 진행할 수 있다. 주목할 만한 주제는 메타클래스가 스몰토크의 개념적 일관성을 보호한다는 점이다. ("모든 것은 객체이므로 모든 것은 어떤 클래스의 인스턴스이기도 하다.") 이러한 일관성은 다른 대부분의 객체 지향 언어에서 발견되는 일관성과는 다르다. C++ 클래스는 객체가 아니다; 가령 그들은 메시지를 수신할 수 없다. C++ 클래스는 시스템에 참여하기보다는 시스템을 설명하는 역할로 제한된다.

다시 언급하지만, 메타클래스와 관련된 세부설명은 제 20장에 싣겠다. 따라서 다음으로 취할 단계는 스몰토크에서 프로그래밍을 시작하는 것이다.


Notes