Smalltalk80LanguageImplementationKor:Chapter 09

From 흡혈양파의 번역工房
Jump to navigation Jump to search
제 9 장 모든 컬렉션 클래스에 대한 프로토콜

모든 컬렉션 클래스에 대한 프로토콜

Object

    Magnitude
        Character
        Date
        Time

        Number
            Float
            Fraction
            Integer
                LargeNegativeInteger
                LargePositiveInteger
                SmallInteger

        LookupKey
            Association

    Link

        Process

    Collection***

        SequenceableCollection
            LinkedList

                Semaphore

            ArrayedCollection
                Array

                Bitmap
                    DisplayBitmap

                RunArray
                String
                    Symbol
                Text
                ByteArray

            Interval
            OrderedCollection
                SortedCollection
        Bag
        MappedCollection
        Set
            Dictionary
                IdentifyDictionary

    Stream
        PositionableStream
            ReadStream
            WriteStream
                ReadWriteStream
                    ExternalStream
                        FileStream

        Random

    File
    FileDirectory
    FilePage

    UndefinedObject
    Boolean
        False
        True

    ProcessorScheduler
    Delay
    SharedQueue

    Behavior
        ClassDescription
            Class
            MetaClass

    Point
    Rectangle
    BitBit
        CharacterScanner

        Pen

    DisplayObject
        DisplayMedium
            Form
                Cursor
                DisplayScreen
        InfiniteForm
        OpaqueForm
        Path
            Arc
                Circle
            Curve
            Line
            LinearFit
            Spline


컬렉션은 객체 집단을 표현한다. 이러한 객체들을 컬렉션의 요소라고 부른다. 예를 들어 Array는 하나의 집합이다. 아래와 같은 Array는

#('word' 3 5 $G (1 2 3))


요소가 5개인 컬렉션이다. 첫 번째 요소는 String, 두 번째와 세 번째는 SmallIntegers, 네 번째는 Character, 다섯 번째는 또 하나의 Array다. 첫 요소 String 또한 컬렉션으로, 이번 경우 4개의 Characters로 된 컬렉션이다.


컬렉션은 Smalltalk-80 시스템에서 프로그래밍에 기본적인 데이터 구조를 제공한다. 어떤 컬렉션의 요소들은 정렬되지 않고, 어떤 컬렉션의 요소는 정렬되어 있다. 정렬되지 않은 요소를 가진 컬렉션 중에서도 Bags는 중복 요소를 허용하고 Sets는 중복을 허용하지 않는다. 객체의 쌍을 연관시키는 Dictionaries도 있다. 요소가 정렬된 컬렉션 중 일부는 요소가 추가되면 외부적으로 순서가 명시되기도 하고 (OrderedCollection, Arrays, Strings), 요소 자체를 기반으로 순서를 결정하는 컬렉션들도 있다 (SortedCollection). 예를 들어, 배열과 문자열에서 공통된 데이터 구조는 정수 색인과 요소를 연관시키고 색인의 정렬에 따라 외부 순서를 가진 클래스에 의해 제공된다.


이번 장에서는 모든 컬렉션이 제공하는 프로토콜을 소개한다. 본 장에 설명된 각 메시지는 컬렉션이 명시적으로 거부하지 않는 한 어떤 유형의 컬렉션에서도 이해된다. 각 컬렉션 유형에 대한 설명은 다음 장에서 소개하겠다.


컬렉션은 요소로 접근하기 위한 네 가지 메시지 범주를 지원한다.

  • 새 요소를 추가하는 메시지
  • 요소를 제거하는 메시지
  • 요소의 발생을 검사하는 메시지
  • 요소를 열거하는 메시지


컬렉션으로 단일 또는 여러 개의 요소를 추가하거나 그로부터 제거하는 것이 가능하다. 또 컬렉션이 비었는지, 아니면 특정 요소를 포함하는지 검사하는 수도 있다. 컬렉션에서 특정 요소가 발생하는 횟수를 결정하는 것도 가능하다. 열거는 컬렉션에서 요소를 제거할 필요 없이 요소로 접근하도록 해준다.


요소 추가, 제거, 검사하기

컬렉션에 기본 프로토콜은 모든 컬렉션 클래스의 슈퍼클래스인 Collection에서 명시된다. Collection 클래스는 Object 클래스의 서브클래스에 해당한다. 요소를 추가, 제거, 검사하기 위한 프로토콜은 다음과 같다.

adding
add: newObject newObject 인자를 수신자의 요소들 중 하나로 포함시킨다. newObject를 응답한다.
addAll: aCollection aCollection 인자의 모든 요소를 수신자의 요소로 포함시킨다. aCollection을 응답한다.
removing
remove: oldObject 수신자의 요소에서 oldObject 인자를 제거한다. oldObject와 동일한 요소가 없는 경우 오류가 발생하였음을 보고하고, 그 외의 경우 oldObject를 응답한다.
remove: oldObject ifAbsent: anExceptionBlock 수신자의 요소에서 oldObject 인자를 제거한다. 여러 개의 요소가 oldObject와 동일하면 하나만 제거된다. oldObject와 동일한 요소가 없는 경우 anExceptionBlock을 평가한 결과를 응답하고, 그 외의 경우 oldObject를 응답한다.
removeAll: aCollection 수신자로부터 aCollection 인자의 각 요소를 제거한다. 각각 성공할 때마다 aCollection을 응답한다. 그 외의 경우 오류가 발생하였음을 보고한다.
testing
includes: anObject anObject 인자가 수신자의 요소 중 하나와 동일한지 응답한다.
isEmpty 수신자가 어떤 요소를 포함하는지 응답한다.
occurrencesOf: anObject 수신자의 요소 중 anObject 인자와 같은 요소가 몇 개인지 응답한다.
Collection 인스턴스 프로토콜


이러한 메시지 사용을 보이기 위해 lotteryA 컬렉션과

(272 572 852 156)


lotteryB 컬렉션을 소개한다.

(572 621 274)


로또에 사용되는 숫자를 표현하는 이 두 개의 컬렉션은 Collection의 서브클래스인 Bag의 인스턴스라고 가정할 것이다. Collection 자체는 모든 컬렉션에 대한 프로토콜을 설명한다는 의미에서 추상적이다. Collection은 요소를 저장하기에 충분한 표현을 제공하지 않으므로 그 모든 메시지의 Collection에 구현을 제공하는 것이 불가능하다. Collection의 정의가 이렇게 불완전하기 때문에 Collection의 인스턴스를 생성하는 것은 유용하지 못하다. Bag은 그 슈퍼클래스에서 구현할 수 없는 메시지의 구현과 요소를 저장하기 위한 표현을 제공한다는 점에서 구체적(concrete)이라 할 수 있다.


모든 컬렉션은 자체 요소의 개수를 응답하기 위해 size에 응답한다. 따라서 아래의 경우 4가 되고,

lotteryA size


아래는 3이 된다.

lotteryB size


메시지를 순서대로 평가하기 위해서는 아래를 사용하면 된다.

표현식 결과 변경된 lotteryA
lotteryA isEmpty false  
lotteryA includes: 572 true  
lotteryA add: 596 596 Bag (272 572 852 156 596)
lotteryA addAll: lotteryB Bag(572 621 274) Bag (272 274 852 156 596 572 572 621)
lotteryA occurrencesOf: 572 2  
lotteryA remove: 572 572 Bag(272 274 852 156 596 572 621)
lotteryA size 7  
lotteryA removeAll: lotteryB Bag(572 621 274) Bag(272 852 596 156)
lotteryA size 4  


add: 와 remove: 메시지는 컬렉션 자체보다는 인자에 응답하므로 계산된 인자로 접근할 수 있음을 주목한다. remove: 메시지는 인자의 모든 발생이 아니라 하나의 발생만 제거한다.


블록은 제 2장에서 소개한 바 있다. remove: oldObject ifAbsent: anExceptionBlock 메시지는 오류가 발생할 경우 컬렉션의 행위를 명시하기 위해 블록을 활용한다. anExceptionBlock 인자가 만일 oldObject가 참조하는 객체가 컬렉션의 요소가 아닐 경우 인자는 평가된다. 이 블록은 오류를 처리하거나 단순히 무시하는 코드를 포함할 수 있다. 예를 들어, 아래 표현식은

lotteryA remove: 121 ifAbsent: []


121이 lotteryA의 요소가 아니라고 결정되면 어떤 일도 수행하지 않는다.

remove: 메시지의 기본 행위는 error: 'object is not in the collection' 메시지를 컬렉션으로 전송하여 오류를 보고하는 것이다. (error: 메시지는 모든 객체에 대한 프로토콜에 명시되므로 모든 컬렉션이 이해한다는 사실을 상기해보자.)


요소 열거하기

컬렉션의 인스턴스 프로토콜에는 컬렉션의 요소를 열거하고 블록의 평가에 각 요소를 제공하는 기능을 지원하는 열거 메시지가 몇 가지 있다. 기본 열거 메시지는 do: aBlock이다. 이는 1인자 블록을 인자로 취하고, 컬렉션의 각 요소마다 블록을 한 번씩 평가한다. 예를 들어 letters가 Characters의 컬렉션이고, Characters 중 몇 개가 a 또는 A인지 알고싶어 한다고 가정해보자.

count  0.
letters do: [:each | each asLowercase = = $a
    ifTrue: [count  count + 1]]


즉, 대문자 또는 소문자 a에 해당하는 요소가 있을 때마다 카운터 count를 1씩 증가시킨다. count의 최종 값이 바람직한 결과일 것이다. Characters를 나타내는 객체들은 유일하므로 상등성보다는 동등성 검사(==)를 사용한다.


모든 컬렉션의 프로토콜에는 기본 열거 메시지의 여섯 가지 개선이 포함되어 있다. 이러한 열거 메시지의 설명에 따르면 결과가 되는 정보를 수집하기 위해 “수신자와 같은 새 컬렉션”이 생성된다고 나타낸다. 이 말은 새로운 클렉션이 수신자와 동일한 클래스의 인스턴스라는 의미다. 가령 select: 메시지의 수신자가 Set이거나 Array라면 응답은 각각 새로운 Set 또는 Array가 된다는 말이다. Smalltalk-80 시스템에서 유일한 예외는 이러한 열거 메시지로부터 새로운 Interval이 아니라 새로운 OrderedCollection을 리턴하는 Interval 클래스의 구현에 존재한다. 이것을 예외로 두는 이유는, Interval의 요소들은 Interval이 처음 생성될 때 같이 생성되기 때문에 기존에 있는 Interval에 요소를 저장하기가 불가능하기 때문이다.

enumerating
do: aBlock 수신자의 요소마다 aBlock 인자를 평가한다.
select: aBlock 수신자의 요소마다 aBlock 인자를 평가한다. aBlock이 true로 평가하는 요소만 수신자와 마찬가지로 새 컬렉션에 수집한다. 새로운 컬렉션을 응답한다
reject: aBlock 수신자의 요소마다 aBlock 인자를 평가한다. aBlock이 false로 평가하는 요소만 수신자와 마찬가지로 새 컬렉션에 수집한다. 새로운 컬렉션을 응답한다.
collect: aBlock 수신자의 요소마다 aBlock 인자를 평가한다. 수신자와 마찬가지로 각 평가마다 블록이 리턴하는 값을 포함하는 새로운 컬렉션을 응답한다.
detect: aBlock 수신자의 요소마다 aBlock 인자를 평가한다. aBlock이 true로 평가하는 첫 번째 요소를 응답한다. true로 평가하는 요소가 없다면 오류를 보고한다.
detect: aBlock ifNone: exceptionBlock 수신자의 요소마다 aBlock 인자를 평가한다. aBlock이 true로 평가하는 첫 번째 요소를 응답한다. true로 평가하는 요소가 없다면 exceptionBlock 인자를 평가한다. exceptionBlock은 어떠한 인자도 필요로 하지 않는 블록일 것이다.
inject: thisValue into: binaryBlock 수신자의 요소마다 binaryBlock 인자를 평가한다. 블록에는 두 개의 인자가 있는데, 두 번째는 수신자로부터 요소이고 첫 번째는 thisValue 인자부터 시작해 블록의 이전 평가 값에 해당한다. 블록의 최종 값을 응답한다.
Collection 인스턴스 프로토콜


각 열거 메시지는 컬렉션의 요소에 관한 정보를 검사하거나 수집하기 위한 메시지 시퀀스를 간단하게 표현하는 방법을 제공한다.


선택하기(Selecting)와 거부하기(Rejecting)

select: 메시지를 이용해 a 또는 A 문자의 발생 횟수를 알아낼 수 있다.

(letters select: [:each | each asLowercase = = $a]) size


다시 말해, a 또는 A에 해당하는 letters의 요소들만 포함한 컬렉션을 생성한 후 결과가 되는 컬렉션의 크기를 응답한다.


reject: 메시지를 이용하여 a 또는 A 문자의 발생 횟수를 알아낼 수도 있다.

(letters reject: [ :each | each asLowercase ~~ $a]) size


이는 letters의 요소들 중 a 또는 A가 아닌 요소를 제거함으로써 컬렉션을 생성하고 결과가 되는 컬렉션의 크기를 응답한다.


select: 와 reject: 중 무엇을 사용할 것인지는 검사에 어떤 표현식이 가장 적합한지를 바탕으로 해야 한다. 선택 검사가 수용(accepptance)과 관련해 가장 잘 표현된다면 select: 이 사용하기 수월하고, 거부와 관련해 가장 잘 표현된다면 reject: 가 사용하기 수월하다. 이번 표현식 예제에서는 select: 가 선호될 것이라 생각할 수 있다.


또 다른 예로 employees가 직원들의 컬렉션이라고 가정하고, 각 직원은 salary라는 메시지에 자신의 총소득을 응답한다고 가정하자. 급여가 $10,000 이상인 직원으로 구성된 컬렉션을 만들기 위해서는

employees select: [ :each | each salary > = 10000]


위와 아래 중 하나를 사용한다.

employees reject: [ :each | each salary < 10000]


결과 컬렉션은 동일하다. select: 와 reject: 중 어떤 메시지를 사용하는지는 프로그래머가 "최소 $10,000" 라는 기준을 통해 무엇을 원하는지에 따라 따라 좌우된다.


수집하기(Collecting)

employees 컬렉션에서 각 직원의 급여를 요소로 가진 새로운 컬렉션을 생성하길 원한다고 가정하자.

employees collect: [ :each | each salary]


결과 컬렉션의 크기는 employees와 동일하다. 새로운 컬렉션의 각 요소는 employees의 해당하는 요소의 급여에 해당한다.


감지하기(Detecting)

employees의 컬렉션에서 급여가 $20,000 이상인 직원 한 명을 찾는다고 가정하자. 아래의 표현식을 이용하면

employees detect: [ :each | each salary > 20000]


해당 직원이 존재할 경우 직원을 응답할 것이다. 존재하지 않는다면 employees는 error: 'object is not in the collection' 라는 메시지를 받을 것이다. 메시지를 제거하는 명세에서와 같이 프로그래머는 성공하지 않은 detect: 에 대해 예외 행위를 명시할 수 있는 선택권을 갖는다. 다음 표현식은 급여가 $20,000가 넘는 한 명의 직원을 응답하고, 직원이 존재하지 않을 경우 nil을 응답한다.

employees detect: [ :each | each salary > 20000] ifNone: [nil]


주입하기(Injecting)

inject:into: 메시지에서 첫 번째 인자는 결과를 알아내는 데에 사용되는 초기값이고, 두 번째 인자는 2인자 블록이다. 첫 번재 블록 인자는 결과를 참조하는 변수를 명명하고 두 번째 블록 인자는 컬렉션의 각 요소를 참조한다. employees 컬렉션에서 직원의 급여를 모두 합하는 경우를 예로 들 수 있겠다.

employees
    inject: 0
    into: [ :subTotal :nextWorker | subTotal + nextWorker salary]


위에서 0의 초기값은 employees 컬렉션에서 각 직원의 급여 값만큼 증가한다. 그 결과 subTotal이 최종값이 된다.


inject:into: 메시지를 이용해 프로그래머는 임시 변수명을 부분적으로 명시하고 결과를 축적할 수 있는 객체의 초기화 구분을 피할 수 있다. 예를 들어 letters 컬렉션에서 Character a와 A의 발생 횟수를 세는 앞의 표현식에서 count라는 카운터를 추가해보자.

count  0.
letters do: [ :each | each asLowercase = = $a
    ifTrue: [count  count + 1]]


이 방법 대신 inject:into: 메시지를 이용하는 방법도 있다. 표현식 예제에서 결과는 count로 누적된다. count는 0에서 시작한다. 다음 문자(nextElement)가 a 또는 A인 경우 count에 1이 더해지고, 그 외의 경우 0이 더해진다.

letters inject: 0
    into: [ :count :nextElement |
        count + (nextElement asLowerCase = = $a
            ifTrue: [1]
            ifFalse: [0])]


인스턴스 생성(Instance Creation)

본 장의 시작 부분에서 새로운 컬렉션을 리터럴로 표현한 예를 제공하였다. 이러한 컬렉션들은 Arrays와 Strings였다. 예를 들어, 배열을 생성하기 위한 표현식은 다음과 같은데,

#('first' 'second' 'third')


각 요소는 리터럴로 표현된 String이다.


new와 new: 메시지는 특정 유형의 컬렉션의 인스턴스를 생성하는 데에 사용할 수 있다. 또 모든 컬렉션에 대한 클래스 프로토콜은 요소가 1개, 2개, 3개 또는 4개인 인스턴스를 생성하는 메시지를 지원한다. 이러한메시지들은 리터럴로 표현되지 않는 유형의 컬렉션을 생성하는 데에 줄임 표기법을 제공한다.

인스턴스 생성
with: anObject anObject를 포함하는 컬렉션의 인스턴스를 응답한다.
with: firstObject with: secondObject firstObject와 secondObject를 요소로 포함하는 컬렉션의 인스턴스를 응답한다.
with: firstObject with: secondObject with: thirdObject firstObject, secondObject, thirdObject를 요소로 포함하는 컬렉션의 인스턴스를 응답한다.
with: firstObject with: secondObject with: thirdObject with: fourthObject firstObject, secondObject, thirdObject, fourthObject를 요소로 포함하는 컬렉션의 인스턴스를 응답한다.
Collection 클래스 프로토콜


가령 Set은 Collection의 서브클래스다. Characters s, e, t에 해당하는 세 개의 요소로 된 새로운 Set을 생성하기 위해서는 아래 표현식을 평가한다.

Set with: $s with: $e with: $t


이와 같이 딱 4개의 인스턴스 생성 메시지를 제공하는 이유는 컬렉션을 시스템 자체에 위치시키는 데에 4라는 숫자가 적합하기 때문이다.


컬렉션 클래스간의 변환

컬렉션 유형간에 허용되는 변환에 대한 전체적인 설명과 이해는 Collection의 모든 서브클래스의 표현에 따라 좌우된다. 여기서는 수신자를 Bag, Set, OrderedCollection, SortedCollection으로 변환하기 위해 모든 컬렉션에 대한 변환 프로토콜에 5개의 메시지가 명시되어 있음을 인지하기만 하겠다. 이러한 메시지들은 Collection 클래스에 명시되는데, 어떤 컬렉션이든 이러한 컬렉션 유형으로 변환하는 것이 가능하기 때문이다. 요소가 정렬되지 않은 컬렉션으로부터 요소가 정렬된 컬렉션으로 변환할 때 요소의 순서는 임의로 설정된다.

변환하기(converting)
asBag 수신자의 요소를 요소로 가진 Bag을 응답한다.
asSet 수신자의 요소를 요소로 가진 Set을 응답한다 (따라서 중복은 모두 제거된다).
asOrderedCollection 수신자의 요소를 요소로 가진 OrderedCollection을 응답한다 (순서는 임의로 이루어진다).
asSortedCollection 수신자의 요소를 요소로 가진 SortedCollection을 응답하되, 요소가 successors와 동일하거나 작도록 (< =) 정렬한다.
asSortedCollection: aBlock 수신자의 요소를 요소로 가진 SortedCollection을 응답하되, 인자 aBlock에 따라 정렬한다.
Collection 인스턴스 프로토콜


따라서 lotteryA 가 아래의 요소를 포함하는 Bag라면

272 572 852 156 596 272 572


아래는

lotteryA asSet


아래의 요소들을 포함한 Set이고

852 596 156 572 272


아래는

lotteryA asSortedCollection


다음과 같이 정렬된 요소를 포함하는 SortedCollection이다 (첫 번째 요소가 가장 왼쪽에 열거된다).

156 272 272 572 572 596 852


Notes