TheArtandScienceofSmalltalk:Chapter 04

From 흡혈양파의 번역工房
Jump to navigation Jump to search
제 4 장 스몰토크 언어

스몰토크 언어

이름에서 알 수 있듯이 스몰토크는 매우 작은 언어이다. 언어 자체에는 정말 별 게 없다. 스몰토크의 모든 힘은 (그리고 복잡성은) 거대한 클래스 라이브러리, 그리고 광범위한 개발 환경으로부터 발생한다. 클래스 라이브러리와 개발 환경은 다음 장들에 걸쳐 살펴볼 것이다. 이번 장은 스몰토크 언어와 관련된다. 여기서는 변수의 선언, 값 할당, 메시지 전송에 사용되는 기본 구성을 설명할 것이다. 이들은 스몰토크 프로그래밍의 기본 요소들이자 메서드와 클래스의 구성요소(building blocks)에 해당한다. 그리고 나면 시스템 코드를 이해하기 위해 이러한 구성요소를 사용하는 방법을 살펴보고 자신만의 메서드와 클래스를 정의할 것이다.


명명 규칙

실제 스몰토크 언어를 살펴보기 전에 스타일과 관련된 충고를 한 마디 언급하는 것이 유용하겠다. 스몰토크 프로그래머들은 주로 클래스, 변수, 메서드를 명명 시 엄격한 규칙을 따른다. 이들은 영어로 된 다수의 단어로 구성된 이름으로 명명할 수 있다. 단어는 단순히 조합되고 각 새로운 단어의 시작은 대문자로 되어 있다. 스몰토커들은 단어의 구분 시 밑줄 표시(_)를 사용하지 않는다. 이름이 대문자로 시작하느냐, 소문자로 시작하느냐는 명명하는 대상의 종류에 따라 달라진다-특수 사례는 진행하면서 때에 맞춰 언급할 것이다. 스몰토크 이름의 예를 몇 가지 소개하겠다:

height schoolHistory ControllerWibhMenu anOrderedCollection


마지막 이름인, anOrderedCollection 을 살펴보자. 스몰토크에서 매우 흔한 명명 규칙의 일례에 해당한다. 변수에 대한 적절한 이름이 없다면 클래스명을 따르되 앞에 'a' 또는 'an'을 붙여 명명한다. 긴 이름도 괜찮다. 사실 긴 이름의 사용을 권장하는데 코드를 읽기 쉬우므로 결국 코드의 재사용도 수월해지기 때문이다. 모든 언어에서 그렇듯 이름이 설명적(descriptive)일수록 좋다.


리터럴과 상수(Literals and Constants)

스몰토크는 수많은 종류의 리터럴을 제공한다. 리터럴은 입력만으로 프로그램에 자유롭게 포함시킬 수 있는 숫자나 문자열과 같다. 여기 스몰토크 숫자를 몇 가지 예로 들어보겠다:

123 3.14 2.789e31 22/7 -0.07


스몰토크는 정수, 부동 소수점 수를 비롯해 조금 특이하게도 분수까지 처리할 수 있다. 이 사실을 인식할 필요가 있는 이유는, 분수가 만일 숫자를 가장 정확하게 표현하는 방법이라면 별도로 요청하지 않는 한 스몰토크가 분수로 표현할 것이기 때문이다. 이는 특히 디버깅 시 자신의 프로그램이 사용 중인 숫자를 봐야 하는 경우 약간 혼란스러울 수도 있다.


스몰토크 문자열은 인용 부호(')를 이용해 표기된다. 문자열 내에는 어떤 문자든 허용되지만 인용 부호를 포함해야 한다면 두 개의 인용 부호를 사용해야 할 것이다. 큰따옴표 (") 안에 문자열을 넣는 실수를 하지 않길 바란다. 스몰토크는 주석의 범위를 정할 때 큰따옴표를 사용하기 때문이다! 문자열과 주석의 예를 각각 들어보겠다:

'Apple'
'Peter's Pepper'
'This is a Smalltalk string.'
"And this is a Smalltalk comment!"


다른 언어에서와 마찬가지로 스몰토크에서도 문자열이 자주 사용된다. 각 character 객체를 리터럴로 처리해야 하는 경우는 덜하다. 이들은 문자 앞에 $ 기호를 붙여서 명시된다. 예를 들자면 다음과 같다: $a, $z, $<. 그 안에 하나의 문자(character)로 된 문자열(string)은 문자 자체와는 다른 객체라는 사실을 기억하라. 즉, 'f는 $f와 같지 않다.


스몰토크는 symbols라 불리는 객체들도 제공한다. 이들은 유일하다(unique)는 점을 제외하면 문자열과 유사하다. 다시 말해, 당신은 동일한 문자 시퀀스를 포함하는 string 객체를 여러 개 생성할 수 있는 반면 주어진 문자 시퀀스로 된 symbol의 인스턴스는 단 하나만 존재할 것이다. 따라서 의도대로 사용-객체와 상태의 명명-하기에 더 효과적이다. 그들의 사용은 스타일의 문제로, 진행하면서 점점 더 명확하게 이해할 수 있을 것이다. C에서의 #defmes 또는 열거 타입(enumerated type)에 익숙해져 있다면 스몰토크에서 기호를 사용하는 방식도 익숙함을 느낄 것이다. 기호의 예를 몇 가지 들어보겠다:

#red #syndicateLisfc ttwaiting


후에 상세히 논하겠지만, 스몰토크 클래스 라이브러리는 많은 종류의 collection 객체를 포함한다. 하지만 그 중에서 하나, 리터럴의 배열은 그 자체를 리터럴로 생성할 수 있다. 예를 살펴보자:

#(2 7.59 'Hello' #resetPending)


이 예제는 네 가지 다른 객체의 배열-두 개의 숫자, 문자열 하나, 기호 하나-을 생성한다. 이 객체들이 어떻게 리터럴이 되는지 주목하라. 배열 중 어떤 객체에도 더 복잡한 것은 넣지 않도록 하라-작동하지 않을 것이다. 컬렉션에 관한 장에서는 여느 객체든 그에 대한 좀 더 일반적인 컬렉션을 만드는 방법을 학습할 것이다.


마지막으로 스몰토크에는 세 가지 특별 상수(constants)가 있다: true, false, nil. 이 셋 중에 하나를 지칭할 때마다 사실은 각각 True, False, UndefinedObject 클래스의 유일한 인스턴스에 해당하는 객체를 지칭하는 것이다. 누구나 짐작할 수 있듯이 true와 false는 부울(Boolean) 상태를 표현하는 데에 사용되는 반면 nil은 '아무 것도 아닌' 혹은 '정의되지 않은'이란 개념을 표현하는 데 사용된다. 이번 장에 실린 다른 내용과 마찬가지로 이 또한 경험이 증가하면서 명확하게 이해할 수 있을 것이다!


스몰토크는 '타입이 없는' 것으로 설명되곤 한다. 혼란스럽겠지만 어떤 타입도 존재하지 않는다는 의미는 아니다. 시스템 내 어떤 객체든 타입을 갖고 있으며, 스몰토크 용어로 하자면 클래스를 갖고 있다고 말할 수 있겠다. 예를 들어, 정수(integers), 문자열(strings), 배열(arrays), 파일(files), 창(windows) 등에 해당하는 객체들이 있다. 타입이 없다는 것은 스몰토크 변수에 해당한다.


여느 다른 언어에서와 마찬가지로 스몰토크 변수들도 이름과 값을 갖고 있다. 이름은 거의 모든 것이 가능하며 몇 가지 규칙을 조건으로 한다. 값은 어떤 객체든 가능하다. 가령 origin이라는 변수가 있다면 이는 부동 소수점 수, 일자, 메뉴 아니면 어떤 종류의 스몰토크 객체든 보유할 수 있다. 그리고 다른 언어들과 마찬가지로 여전히 변수를 선언해야 하지만 타입이 아니라 이름만 부여하면 된다. 'interger 타입의 age라는 변수를 달라'고 말하지 않아도 된다. 그저 'age라는 변수를 달라'고 말하면 된다.


타입이 없다는 것(typelessness)이 처음에는 혼란스럽고 심지어 불안해 보일지도 모른다. 사실 이러한 점이 바로 스몰토크 코드의 힘과 유연성에 기여하는 요소인데도 말이다. 이는 제 2장에서 살펴본 다형성의 사용을 촉진하고, 당신이 상상할 수 있는 어떤 유형의 버그도 전혀 야기하지 않는다. 타입이 없다는 점을 사용하면 곧 그것이 부여하는 힘을 사랑하게 될 것이다!


우리는 변수의 값이 객체일 경우 객체가 변수 '안에' 있다고 이야기하곤 한다. 하지만 때로는 변수를 객체를 향한 '포인터'라고 이야기하는 편이 편하다. 이 두 가지 개념은 사실상 같다. C와 달리 스몰토크에는 포인터의 개념이 (혹은 필요가) 없다. 도움이 된다면 스몰토크에서는 모든 것이 참조에 의해 전달된다는 사실을 기억하라.


스몰토크에는 여섯 가지 유형의 변수가 있다: 임시; 인스턴스; 클래스; 클래스-인스턴스; 전역; 그리고 풀(pool). 각 변수 유형마다 다른 범위와 수명(lifetime)을 가지며, 다른 방식으로 선언된다. 선언 시에는 nil 이라는 특수 값으로 초기화된다. 이러한 사실을 사용하도록 선택하든, 새로운 변수를 다른 값으로 설정하여 코드를 시작하든 그것은 프로그래밍 스타일의 문제이며 전적으로 당신에게 달려 있다. 이제 변수 유형을 하나씩 살펴보자:


임시 변수

임시 변수들은 가장 작은 범위와 가장 짧은 수명을 가진다. 이들은 단일 메서드 내에서 선언되며, 해당 메서드 내부에서만 보인다. 메서드의 실행 중에만 지속되고, 그 값은 다중 실행에 걸쳐 보존되지 않는다. 다른 언어들에서 '로컬' 변수로 생각하면 되겠다. 규칙상 임시 변수의 이름은 소문자로 시작된다. 임시 변수들은 그들이 사용될 각 메서드가 시작될 때 모두 함께 선언된다. 두 개의 수직 막대(| |) 사이에 명명을 통해 이루어진다. 예를 들어,

| size   qualityOfLife   ambition |


위는 size, qualityOfLife, ambition이라는 세 개의 변수를 선언할 것이다. 변수들에게 '타입'이 주어지지 않는다는 사실을 주목하라. 세 개의 변수 모두 어떤 클래스의 객체든 보유할 수 있다. 이 변수들은 현재 메서드의 실행 시간 동안 선언되며, nil 로 초기화될 것이다.


인스턴스 변수

제 2장에서 살펴보았듯, 객체마다 캡슐화되는 것으로 생각하는 유형의 변수들이다. 객체에 private하지만 그 객체의 모든 메서드들에겐 보인다. 인스턴스 변수들은 그들을 포함하는 객체가 존재하는 한 존재하고, 그들의 값을 유지한다. 그 시간은 전적으로 해당하는 객체에 따라 좌우되며, 매우 짧거나 매우 길 수 있다. 인스턴스 변수는 개발 환경의 일부에 해당하는 브라우저(browsers) 중 하나를 이용해 객체의 클래스에서 선언된다. 브라우저에 관해서는 나중에 살펴볼 것이다. 클래스의 인스턴스마다 클래스에서 선언된 인스턴스 변수에 대해 고유의 복사본(copy)을 갖고 있다는 사실을 기억하라. 이름은 같지만 값은 다르다. 인스턴스 변수명은 항상 소문자로 시작된다.


클래스 변수

클래스 변수는 클래스에서만 존재한다는 점을 제외하면 인스턴스 변수와 유사하다. 클래스 자체와 클래스의 인스턴스는 이를 볼 수 있다. 허나 인스턴스 변수와 달리 각 인스턴스는 동일한 변수를 본다. 이는 클래스와 그 서브클래스들의 모든 인스턴스들이 데이터를 공유하도록 해준다. 클래스 변수명은 대문자로 시작한다.


클래스-인스턴스 변수

이 유형의 변수는 클래스에 의한 직접 접근만 (클래스의 인스턴스는 접근 불가) 가능하다. 변수의 값은 상속 계층구조에서 하층으로는 공유되지 않는다. 클래스 변수와 마찬가지로 대문자로 시작된다. 혼란스럽게 명명되고 사용되는 경우가 드물기 때문에 아래의 예제가 이해가 안 된다면 클래스-인스턴스 변수를 사용해야 한다고 느낄 경우 다시 돌아가길 바란다.


A라는 클래스에 B와 C라는 두 개의 서브클래스가 있다고 가정하자. 클래스 A는 하나의 클래스 변수, 하나의 인스턴스 변수, 하나의 클래스-인스턴스 변수를 정의한다. 이 정의에 따라 다음이 적용된다:


클래스 A, B, C와 A, B, C의 모든 인스턴스는 동일한 클래스 인스턴스를 공유할 것이다. A, B, C의 인스턴스마다 인스턴스 변수에 대한 고유의 private 복사본을 가질 것이다. 다시 말해, 정확히 하나의 클래스 변수 복사본, 인스턴스 수만큼의 인스턴스 변수 복사본, 클래스의 수만큼 (이번 사례의 경우 3개) 클래스-인스턴스 변수 복사본이 존재할 것이다. 인스턴스 변수는 인스턴스에서만 보이고, 클래스 변수는 클래스와 인스턴스에서만 보이며, 클래스-인스턴스 변수는 클래스에서만 보인다는 사실을 기억하라. 헷갈리는 것이 당연하다!


전역 변수

스몰토크 전역 변수는 다른 언어들의 전역 변수와 같다. 한 번만 선언되고 (처음 참조 시 즉각적으로 선언되거나 Smalltalk라는 객체로 메시지를 전송함으로써), '영원히' 지속되며, 어디서든 볼 수 있다. 규칙상 전역 변수의 이름은 항시 대문자로 시작해야 한다. 주의만 기울인다면 전역 변수를 마음껏 사용해도 된다. 시스템 내 모든 클래스명은 사실 전역 변수이며, 다른 중요한 시스템 전역 변수도 여러 개가 있다. 전역 변수에는 어떤 것도 할당해선 안 되는데, 이를 어길 경우 예상치 못한 결과가 발생할 것이다!


풀 변수

풀 변수는 프로그래머가 접근을 허용한 특정 클래스 집합으로 범위가 제한된다는 점만 제외하면 전역 변수와 비슷하다. 따라서 클래스 변수보다 더 유연하지만 전역 변수보다 덜 위험하다. 풀 변수들은 풀 변수의 집합을 정의하는 'dictionaries'에서 그룹화된다.


두 개의 인스턴스 변수(size와 colour), 하나의 클래스 변수(Material), 하나의 pool dictionary (Shapes)를 정의하는 클래스 정의를 (Square라 불리는 클래스) 하나 예로 들어보겠다. 현재로선 정의의 구문을 신경쓰지 않는데, 다음 장에서 다룰 것이기 때문이다.

Objeot subclass: ^Square
	instanceVariableNames: 'size colour'
	classVariableNames: 'Material'
	poolDictionaries: 'Shapes'


특수 변수 또는 Psuedo-변수

스몰토크에는 소위 Pseudo-변수라 불리는 것이 두 개가 있다: self와 super가 그것이다. 이것으로는 어떤 것도 할당이 불가하기 때문에 사실 변수가 아니다. 하지만 그것이 사용되는 맥락에 따라 값이 변하기 때문에 상수도 아니다.


Pseudo-변수 self가 메서드에 나타날 때는 'me'를 의미한다. 메서드는 메시지의 전송을 통해서만 호출할 수 있다. 따라서 객체 내 메서드가 객체의 또 다른 고유의 메서드를 호출하길 원할 경우 (사실 극히 드문 경우긴 하나) 자신에게 메시지를 전송해야 한다. 이를 가능하게 하려면 스스로를 self로 참조해야 한다.


self와 유사한 것이 바로 super다. 객체가 super에게 메시지를 전송할 때는 사실상 '이 메서드에 대한 내 고유의 정의부를 호출하려는 것이 아니라 내 슈퍼클래스의 정의부를 호출하고 싶다'를 의미한다. 이는 상속과 오버라이딩을 이용해 서브클래스 내 슈퍼클래스의 메서드를 단순히 대체하는 대신 확장하는 데 사용할 수 있기 때문에 유용하다. 다시 말해, 서브클래스는 그 슈퍼클래스 내 메서드와 동일한 이름으로 된 메서드를 정의할 것이다. 이 메서드는 (super를 이용하여) 슈퍼클래스의 메서드를 호출한 후 스스로를 추가로 처리할 것이다.


메시지 전송

스몰토크에서 거의 모든 일은 객체로 메시지를 전송하여 이루어진다. 먼저 객체를 명명한 후 메시지를 명명하는 일로 이어진다. 호출되는 메서드가 매개변수(parameter)를 필요로 하는 경우, 매개변수도 전송이 가능하다. 스몰토크는 매개변수를 메서드명에 포함시킴으로써 매개변수를 전송하는 특별한 방법을 지원한다. (가상) 메시지 전송의 예를 몇 가지 들어보겠다:

MyStorageSystem initialize.
    aSquare increaseSizeBy: 34.
    manager employ: 'Aristotle' as: 'philosopher'.
    Database open: 'people.dat' using: key rwMode: 7.


이 메시지들이 실제로 무엇을 성취하는지는 관심이 없다 (이를 생성하지 않는 한 시스템에 존재하지 않을 것이므로 시도하지 말길 바란다!). 우리가 관심 있는 것은 메시지 표현식의 구조이다. 이를 이해하기 위해 C, BASIC, Pascal, COBOL과 같은 절차적 언어에서 동일한 유형으로 된 표현식을 비교하고자 한다. 스몰토크는 이런 절차적 구문을 지원하지 않으므로, 도움이 되지 않는다면 무시해도 좋다.


첫 번째 예문은 간단하다. 이는 MyStorageSystem이라는 객체로 initialize 메시지를 전송한다. 이 코드 조각에서는 알 수 없지만 MyStorageSystem 은 아마도 전역 변수일 것이다. 이 변수 내 객체가 initialize라는 메서드를 갖고 있다면 그것을 실행할 것이다. 절차적 프로그래밍에 익숙한 이라면 해당 메시지는 매개변수를 취하지 않는 프로시저 initialize-initialize ( )-를 호출하는 것과 같다고 생각하면 되겠다. 유일한 차이는, 객체 지향 프로그래밍의 경우 initialize에 대한 수많은 구현부(implementations)가 존재할 수 있다는 점이다. 실제로 실행되는 구현부는 MyStorageSystem 의 클래스에 따라 좌우된다.


두 번째 예제에서 aSquare라는 객체로 (인스턴스 변수나 임시 변수일 것이다) 34라는 변수와 함께 increaseSizeBy :라는 메시지를 전송한다. 메시지명 끝에 콜론(:)을 주목하라. 이는 메시지명의 일부로서, 매개변수가 따라옴을 명시한다. 콜론은 프로시저 호출의 괄호와 같이 추가 구문에 그치는 것이 아니라 메시지명의 일부라는 점을 기억하는 것이 중요하다. 이를 강조하기 위해 스몰토커들은 메서드를 이야기할 때 콜론을 발음한다-'increase-size-by-colon'. 콜론만 제외하고 동일한 이름으로 된 두 개의 메서드는 (colour와 colour:) 두 개의 다른 메서드인 것이다. 하나는 매개변수를 취하지만 하나는 그렇지 않다. 이 둘은 완전히 다른 일을 수행하도록 정의할 수 있다. 절차적 프로그래밍에서 이러한 메시지 표현식을 비유하자면 increaseSizeBy(34)를 들 수 있는데, 다시 말하지만 OOP에선 increaseSizeBy: 의 많은 버전이 가능하다.


세 번째 예가 흥미롭다. 여기서는 manager라 불리는 객체에 두 개의 매개변수, 'Aristotle'과 'philosopher'를 취하는 메시지가 전송된다. 메서드명은 employ:as:이다 ('employ-colon-as-colon'으로 발음). 메시지를 구성하기 위해 매개변수가 메서드명에 어떻게 포함되는지를 주목하라. 이것은 스몰토크의 특이한 기능이지만 표현식을 매우 명확하게 읽을 수 있도록 해준다 (처음엔 공감하지 않을지도 모르겠지만!). 절차적 프로그래밍에서 이에 상응하는 것으로는 employs('Aristotle', 'philosopher')를 들 수 있겠다.


마지막 예는 메서드가 필요로 하는 매개변수의 수만큼 매개변수의 내포(embedding)를 확장시키는 방법을 보여준다. 이번 사례에서는 open:using:rwMode: 메서드가 세 개의 매개변수를 취한다: 문자열 'people, dat' ; 키(변수); 그리고 7. 절차적 언어에서 이에 상응하는 표현식으로 openUsingrwMode('people, dat*, key, 7)를 들 수 있겠다.


각 예제 메시지는 위의 내용을 마침표 (.) 또는 공백 문자로 끝맺은 다음 전송한다. 이는 평가해야 할 메시지 표현식이 더 있을 경우 표현식을 다음 표현식으로부터 분리시키는 데 꼭 필요하다. 평가하는 메시지 표현식이 더 이상 없는 경우에는 선택적이다 (예를 들어 메서드의 끝 또는 즉시 실행되는 단일 행으로 된 코드 끝에).


스몰토크에선 모든 메시지 표현식에 값이 있다. 메시지 표현식을 실행하면 (또는 스몰토크식으로 말해 메시지 표현식을 평가하면) 메서드가 실행된다. 각 메서드는 완료 시 객체를 리턴한다. 이렇게 리턴된 객체는 표현식의 값으로서 참조된다. 때로는 메서드 작성자가 무엇을 리턴할지 분명하게 명시하기도 할 것이다. 명시할 때에는 윗방향키 또는 탈자 기호(^) 문자를 이용한다. 예를 들어, 메서드에서 ^rue 라는 표현은 메서드의 종료를 야기하고 true를 리턴한다. 다른 메서드들은 *를 포함하지 않을지도 모른다. 이번 경우 시스템은 메서드를 완료시킨 후 self-메시지가 전송된 객체 또는 수신자-를 리턴한다.


메시지의 리턴 값은 연산자(operator) :=를 이용해 변수로 할당될 수 있다. 일부 프로그래머들은 이 연산자를 'becomes(~가 되다)'라고 부르고, 어떤 프로그래머들은 간단히 'colon-equals(콜론 이퀄)'라고 부른다. 몇 가지 예를 생각해보면 다음과 같다:

Total := net + tax.
aForm := Form with: people size: 15.
MyObject := YourObject.


첫 번째 예제에서 net와 tax의 값은 합해지고 결과는 Total로 할당된다. 두 번째 예에서는, with:size: 메시지를 Form으로 전송한 결과가 aForm 으로 할당된다. 이것이 사실은 새 인스턴스를 만드는 데 사용되는 유형의 표현식이라는 사실을 후에 살펴볼 것이다.


세 번째 예제는 매우 중요한 사실을 상기시켜준다. := 연산자는 할당되는 객체의 복사본을 만들지 않는다. YourObject가 특정 객체를 포함한다면 세 번째 예제가 평가된 후 MyObject가 동일한 객체를 포함할 것이다. 다시 말하자면, 두 개의 변수 MyObject 와 YourObject 가 '가리키는' 하나의 객체만 있을 것이란 의미다. 주로 이것은 정확하게 의도한 효과이다. 하지만 두 변수가 동일한 객체를 포함한다는 사실을 잊을 경우 심각한 버그가 발생할 수도 있다! 이 둘을 구분된 객체로 취급하여 둘 중 하나를(예: MyObject) 변경하려고 시도할 경우 나머지 객체도 (YourObject) 똑같이 변경되는 것을 보고 놀람을 금치 못할 것이다! 이와 같은 버그를 찾아내는 데에는 많은 시간이 소요되므로 주의하길 바란다. 새 객체를 변수들 중 하나로 할당 시 (MyObject := 'Hello'와 같은 실행) 나머지 변수는 변경되지 않고 여전히 이전 객체를 '가리킨' 채로 남는다.


메시지 결합하기

모든 메시지 표현식은 값을 가진다는 사실은 메시지를 여러 유용한 방법으로 결합할 수 있음을 의미한다. 예를 들어, 한 메시지가 다른 메시지를 뒤따를 수 있다. 이번 사례에서 두 번째 메시지는 첫 번째 메시지 결과에 해당하는 객체로 전송된다. 예를 들어보겠다:

aTriangle height asInteger.


이번 사례에서는 height 메시지가 aTriangle로 전송되고, asInteger 메시지는 height 메시지가 리턴하는 객체로 전송된다. 이러한 유형의 구성을 체이닝(chaining)이라 부른다. 절차적 용어로 생각하고 싶다면, asInteger(height(aTriangle)) 과 비슷하다고 보면 되겠다.


표현식 예들이 어떻게 좌측부터 우측으로 실행되는지를 주목하라. 항상 그런 것은 아니다. 스몰토크는 표현식이 평가되는 순서를 조절하는 약간 특이한 선행 규칙을 사용한다. 처음엔 이상하게 보일지 모르지만 곧 익숙해질 것이다:


메시지 표현식은 세 가지 타입으로 나뉜다: 단항-매개변수를 취하지 않는 메시지; 이항-예로 +, *, >=를 들 수 있다; 키워드-메서드명에서 콜론 다음에 하나 또는 그 이상의 매개변수를 취하는 메시지. 복합 표현식에서는 단항 메시지가 먼저 평가된다. 단항 메시지의 수가 하나 이상일 경우 좌측부터 우측으로 실행된다. 그 다음으로 이항 메시지가 평가되는데, 이 또한 좌측부터 우측으로 실행된다. 마지막으로 키워드 메시지가 평가되고, 마찬가지로 좌측에서 우측으로 진행된다. 여느 다른 언어에서와 마찬가지로 괄호를 이용해 평가의 순서를 변경할 수 있다. 이번 경우, 가장 안쪽 괄호에 포함된 표현식이 먼저 평가된다 (일반 스몰토크 규칙에 따라). 예를 들어보겠다:

MyCollection add: Pyramid new initialize.
(book openAtPage: 1+2*3) print.
(agenda item:?) title: "Plans'.


첫 번째 사례에서 new 메시지는 Pyramid로 전송된다. 이후, 결과가 되는 객체가 initialize로 전송된다. initialize가 리턴하는 객체를 이제 add:라는 메시지에서 매개변수로 사용하여 MyCollection으로 전송된다. 단항 메시지들이 (new와 initialize) 먼저 평가된 후 (좌측에서 우측으로) 키워드 메시지가 (add:) 평가됨을 주목하라.


두 번째 예제에서는 괄호 내용이 먼저 평가된다. 괄호에는 이항 메시지 두 개와 (+와 *) 키워드 하나가 (openAtPage) 위치한다. 이항 메시지가 먼저 좌측부터 우측으로 평가되어 (일반 산술 순위에 따라 7이 아닌!) 9의 값을 제공한다. 이후 openAtPage: 9 메시지가 book으로 전송되고, 마지막으로 메시지가 리턴하는 객체가 무엇이든 그 곳으로 print 메시지가 전송된다. 괄호가 없다면 순서가 달라졌을 것이다. 시스템은 print 메시지를 먼저 3으로 전송하여 시작했을 것이다. 그리고 1과 2를 추가하여, 3으로 print를 전송하여 리턴된 값에 그 결과를 곱했을 것이다. 이는 분명히 오류를 발생시킬 것이다!


세 번째 예는 표현식을 좌측부터 우측으로 곧바로 평가하고자 할 때에도 괄호가 종종 필요함을 보여준다. 이번 사례에서는 item:? 메시지가 agenda로 전송되고, 결과가 되는 객체가 title: 'Plans' 메시지로 전송되길 바란다. 괄호가 없다면 시스템은 item:? Title: 'Plans' 메시지를 agenda로 전송하게 되고, 이는 전혀 다른 결과를 낳는다 (item: 과 title: 두 개의 구분된 메서드가 아니라 item:title! 라는 단일 메서드를 호출할 것이기 때문이다). 이러한 혼동은 스몰토크가 메서드명에 매개변수를 내포하는 방식 때문에 발생한다.


스몰토크에서 메시지를 결합하는 두 번째 방법은 캐스케이딩(cascading)이라 부른다. 계단식(cascaded) 메시지에서는 하나의 메시지 다음에 세미콜론(;)이 붙고 그 다음에 메시지가 따라온다. 이번 사례의 경우, 잇따른 메시지들이 첫 번째 수신자, 즉 체이닝과 달리 첫 번째 메시지로부터 리턴된 객체로 전송된다. 두 개의 메시지 표현식에서 무엇이 차이가 나는지 생각해보라:

diskController reset initialize startRunning.
diskController reset; initialize; startRunning.


체이닝의 경우, diskController에 reset 메시지가 전송된다. 이후 메시지로부터 리턴된 객체로 initialize 메시지가 전송된다. 그리고 initialize가 리턴한 객체로 startRunning 메시지가 전송된다. 두 번째 방법인 캐스케이딩의 경우, diskController로 reset 메시지가 전송된다. 이후 diskController로 initialize가 전송되고 잇따라 startRunning이 전송된다. reset과 initialize가 self를 리턴할 경우 (이번 경우 diskController) 이 두 표현식은 동일하다. reset과 initialize 가 self가 아닌 결과를 리턴한다면 두 표현식은 동일하지 않다. 생각해보라...


두 개의 메시지 결합 기법 중에서 (체이닝과 캐스케이딩), 세미콜론이 붙지 않는 체이닝이 현재까진 좀 더 흔하며, 거의 모든 곳에서 사용된다. 캐스케이딩은 극히 드물게 사용되는데, 부분적으로는 당신이 사용하고자 하는 많은 곳에서 메서드가 self를 (최소한 기본값으로) 리턴하기 때문에 체이닝을 대신 사용할 수 있기 때문이다. 어쨌든 시스템 코드에서 캐스케이딩을 목격할 것이며 때로는 자신의 코드에서 유용하게 사용될 수도 있으므로 기억하는 편이 유용하다.


원시 연산(Primitive Operations)

스몰토크 클래스 라이브러리에서 메서드를 살펴보는 동안 아래와 같은 표현식을 목격하게 될 것이다:

<primitive; 63>


이는 가상 머신 자체가 지원하는 원시 연산(또는 간단히 primitives)으로의 호출이다. 보통은 매우 저수준의 연산을 실행하는 데 사용되는데, 해당 메서드의 이름으로 짐작할 수 있을 것이다. 메서드에서 이러한 표현식을 따르는 코드는 원시 연산이 실패할 때에만 실행된다. 다른 언어로 호출하기 위해 사용자가 정의한 원시 연산을 추가할 때에만 실제로 이러한 표현식을 작성할 필요가 있다. 그렇지 않으면 이러한 표현식이 생각나더라도 무시하는 수밖에 없다.


코드의 블록

여기서 살펴봐야 할 스몰토크 언어의 마지막 부분은 블록(blocks)이라는 개념이다. 스몰토크 코드가 어떻게 메서드라는 덩어리(chunks)로 작성되는지는 이미 살펴보았다. 이러한 메서드들은 클래스에서 정의되고, 그러한 클래스들의 인스턴스들에게 메시지를 전송함으로써 실행된다. 어떻게 보면 메서드들은 그것을 정의하는 객체로 결합되어 있다. 하지만 스몰토크 코드를 블록이라는 덩어리로 정의하는 것도 가능하다. 이러한 코드 조각은 특정 클래스와 연관되지 않지만 의당 객체에 해당한다. 런타임 시에 생성되고 전달되어 한 번 또는 여러 번 실행된 후 버려진다.


블록은 (BlockClosure 클래스에 의해 구현) 스몰토크의 매우 강력한 기능이지만 다른 많은 언어에서 블록에 대해 직접적으로 유추할 수 있는 대상이 없다. 하지만 C에서 프로그래밍하는 데 익숙하다면 블록을 함수에 대한 포인터처럼 사용되는 것으로 생각하면 되겠다. 사용하는 방법은 여기서 살펴보지 않을 것이며, 추후 다루도록 하겠다. 당장은 블록을 생성하고 실행하는 방법만 살펴보자. 블록 객체의 생성은 그 실행과는 구분된다는 사실을 주목하라. 어떻게 보면 블록은 지연된(deferred) 실행 형태를 나타낸다. 세 가지 블록 예를 들어보겠다:

aBlock := [Recycler initialize].

HyBlock := [EsanObject | anObject print].

anotherBlock := [:parm1 :parm2 | | temp |
			temp := pann1 incorporate: parm2.
			temp rehash.
			].


첫 번째 예는 실행하면 Recycler로 initialize 메시지를 전송하게 될 간단한 블록을 생성한다. 블록은 (블록이 리턴하는 값이 아니라 코드의 블록 자체는) aBlock이라는 변수에 놓인다. 두 번째 예는 블록을 생성하여 MyBlock이란 변수에 넣는다. 블록은 하나의 매개변수를 취한다 (anObject 앞에 콜론이 붙고 뒤에 | 가 따라온다). 블록은 아직 실행되지 않았지만 일단 실행되면 그것이 전달되는 매개변수로 print 메시지를 전송할 것이다. 세 번째 예는 두 개의 매개변수를 취하는 블록을 생성하고, temp라는 임시 변수를 정의한다 (두 개의 | 사이에). 이 예제들과 마찬가지로 블록이 어떤 일을 실행할 것인지는 염려하지 말고 블록이 어떻게 구성되는지만 살펴보라.


이렇게 바로 블록이 생성된다. 이제 블록이 어떻게 실행되는지 살펴보자. 사실 블록을 생성하는 방법은 많다-가장 간단한 방법은 value 메시지의 변형자(variant)로 전송하는 방법이다. 예를 들면 다음과 같다:

HyBlock value:
	'This string will be sent the message print'.


블록은 사실 0부터 255 까지 인수(argument) 중 어느 것이나 취할 수 있다. 인수의 수에 따라 다른 버전의 'value' 메시지를 사용해야 한다. 몇 가지 변형자를 소개하겠다:

value 인수가 없을경우,

value: anObject 인수가 하나일 경우,

value: objecfcl value: object2 인수가 두개인 경우,

value: objl value: obj2 value: ojb3 ...인수가 세개인 경우,

value WitchArgumenfcs: argArray ...인수가 세개 이상인 경우.


때로는 자신이 생성한 블록으로 value를 (또는 value:를) 전송하지 못할 때도 있다-블록을 전달한 객체에 의해 실행될 것이다. 본 책의 뒷부분에서 이와 관련된 예를 충분히 살펴볼 것이다. 블록을 실행하는 또 다른 방법으로는 제어 구조를 구현하는 효과를 가진 방법들과 스몰토크 내에 구분된 프로세스를 갖도록 해주는 방법들이 있다.


여기까지 우리는 명명 규칙을 살펴보고 리터럴, 상수, 변수, pseudo-변수, 할당(assignment), 메시지 표현식과 블록을 설명함으로써 스몰토크 언어를 전체적으로 살펴보았다. 이것이 전부다.


지금쯤이면 클래스 정의, 산술, 입/출력, 제어 구조 등은 왜 논하지 않았는지 어리둥절할 것이다. 사실 이러한 부분들은 언어에 속하는 것이 아니라 (다른 수천 개의 연산을 비롯해) 스몰토크 클래스 라이브러리에서 구현된다. 즉, 대부분의 다른 언어에서와 달리 앞에 열거한 요소들의 정의를 당신이 눈으로 확인하여 즉시 이해할 수 있음을 의미한다. 이는 기본 연산이 정확히 어떤 일을 할 것인지 궁금할 때마다 매뉴얼을 뒤적이지 않아도 되게끔 해주기 때문에 매우 유용한 기능이다.


물론 어떤 유형의 연산이 존재하는지는 이해할 필요가 있는데, 이는 제 6장-스몰토크 클래스 라이브러리-에서 요약한 후, 그 뒤에 실린 여러 장들에 걸쳐 이러한 연산 유형들을 상세히 살펴볼 것이다. 우선 다음 장은 시스템 내 소스 코드를 살펴보고 스몰토크 개발 환경을 이용해 자신만의 프로그램을 개발하는 방법을 소개하겠다.


Notes