Smalltalk80LanguageImplementationKor:Chapter 07

From 흡혈양파의 번역工房
Jump to: navigation, search
제 7 장 길이

길이(Linear Measures)

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


Smalltalk-80 시스템은 선형적 순서로 무언가를 측정하는 객체를 표현하는 클래스를 몇 가지 제공한다. 이렇게 측정 가능한 수량이 실제로 사용되는 경우는 (1) 날짜나 시간과 같은 임시적 양, (2) 거리와 같은 공간적 양, (3) 실수나 유리수와 같은 수치량을 들 수 있다.


Magnitude 클래스

어떤 수가 다른 수보다 작은가? 어떤 일자가 다른 일자보다 앞에 오는가? 어떤 시점이 다른 시점보다 앞서는가? 어떤 문자가 알파벳에서 다른 문자보다 앞에 오는가? 어떤 거리가 다른 거리보다 같거나 짧은가?


이러한 질문에 답하기 위한 일반적인 프로토콜이 Magnitude 클래스에서 제공된다. Magnitude는 선형적 크기를 따라 비교되어야 하는 기능을 가진 객체를 위한 프로토콜을 제공한다. Magnitude 클래스의 서브클래스로는 Date, Time, Number가 있다. Character 클래스(문자열의 요소)와 LookupKey(dictionary 연관의 키) 또한 Magnitude의 서브클래스로서 구현된다. Character는 시스템 내 변경할 수 없는 객체로서 자체가 흥미롭기 때문에 이번 장에서 소개하며, LookupKey는 그에 비해 관심도가 떨어지므로 후에 컬렉션을 다루면서 함께 살펴보도록 하겠다. Distance 클래스는 실제 Smalltalk-80 시스템에선 제공되지 않는다.

비교하기
< aMagnitude 수신자가 인자보다 작은지 응답한다.
< = aMagnitude 수신자가 인자보다 작거나 같은지 응답한다.
> aMagnitude 수신자가 인자보다 큰지 응답한다.
> = aMagnitude 수신자가 인자보다 크거나 같은지 응답한다.
between: min and: max 수신자가 min 인자보다 크거나 같은지, 그리고 max 인자보다 작거나 같은지 응답한다.
Magnitude 인스턴스 프로토콜


Magnitude 는 두 개의 수량화 가능한 객체의 상등성(equality)을 비교하기 위한 메시지 =를 그 슈퍼클래스 Object로부터 상속받지만 모든 Magnitude 의 유형은 이 메시지를 재정의해야만 한다. Magnitude 클래스에서 =와 연관된 메서드는 다음과 같다.

self subclassResponsibility


Magnitude 의 서브클래스가 =를 구현하지 않으면 서브클래스의 인스턴스로 메시지를 전송하려 할 경우 서브클래스가 슈퍼클래스에 명시된 바와 같이 메시지를 구현해야 한다는 특수 오류 메시지가 발생할 것이다.


Magnitude 유형의 인스턴스는 선형적으로 측정 가능한 두 객체 중 무엇이 크고 작은지 결정하는 메시지에 응답할 수도 있다.

검사하기
min: aMagnitude 수신자와 인자 중 크기가 작은 것을 응답한다.
max: aMagnitude 수신자와 인자 중 크기가 큰 것을 응답한다.
Magnitude 인스턴스 프로토콜


상등성 비교 연산, ==, ~=, ~~에 대한 프로토콜은 Object 클래스로부터 상속됨을 주목한다. Integers를 Magnitudes의 유형으로 사용하면 아래와 같다.

표현식 결과
3 < = 4 true
3 > 4 false
5 between: 2 and: 6 true
5 between: 2 and: 4 false
34 min: 45 34
34 max: 45 45


프로그래머는 Magnitude의 인스턴스를 생성하지 않고 그 서브클래스만 생성한다. 이는 Magnitude가 그것이 명시하는 메시지를 모두 구현할 수 없으며 사실상 self subclassResponsibility 표현식에 의해 하나 또는 그 이상의 메시지를 구현하기 때문이다.


Date 클래스

이제 Magnitudes의 일반적인 프로토콜을 정의하였으니 구체적인 linear measuremet에 관한 문의와 산술을 지원하는 추가 프로토콜을 추가하는 것이 가능하다. 가장 먼저 살펴볼 개선은 Date 서브클래스다.


Date의 인스턴스는 율리우스력의 시작부터 특정 일자를 나타낸다. 일자는 특정 월과 연도로 존재한다. Date 클래스는 다음과 같은 몇 가지 명백한 정보에 대해 알고 있다. (1) 일주일에는 7일이 있고, 각 일자는 상징적인 이름과 색인 1, 2, …, 7 중 하나를 갖는다. (2) 1년에는 12개월이 있고, 각 월은 상징적 이름과 색인 1, 2, …, 12 중 하나를 갖는다. (3) 월은 28, 29, 30, 31일 중 하나로 되어 있다. (4) 특정 연도는 윤년이 될 수 있다.


Date 객체에 제공되는 프로토콜은 특정 Date를 비롯해 Dates에 관한 일반적 문의를 지원한다. Date와 Time 모두 특별한 지식을 요하는 시스템의 클래스 예제들로, 그 인스턴스가 아니라 클래스 자체에서 접근이 가능하다. 이러한 "class protocol"(클래스 프로토콜)은 클래스의 메타클래스에 명시된다. 먼저 일반적 문의를 지원하는 Date의 클래스 프로토콜을 살펴보자.

일반적 문의
dayOfWeek: dayName 일주일 중에서 dayName 인자로 명명된 요일의 색인인 1, 2, …, 7 중 하나를 응답한다.
nameOfDay: dayIndex dayIndex 인자를 색인으로 가진 요일명을 나타내는 Symbol을 응답하는데, 1은 월요일, 2는 화요일을 나타내는 식이다.
indexOfMonth: monthName 연중에서 monthName 인자로 명명된 월의 색인인 1, 2, …, 12 중 하나를 응답한다.
nameOfMonth: monthIndex monthIndex 인자를 색인으로 가진 월명을 나타내는 Symbol을 응답하는데, 1은 1월, 2는 2월을 나타내는 식이다.
daysInMonth: monthName forYear: yearInteger yearInteger 연도에서 monthName이라는 이름을 가진 요일 수를 응답한다 (윤년을 설명하기 위해서는 연도가 이해되어야 한다).
daysInYear: yearInteger yearInteger 연도에 일수를 응답한다.
leapYear: yearInteger yearInteger 연도가 윤년일 경우 1을, 그 외의 경우 0을 응답한다.
dateAndTimeNow 첫 번째 요소가 현재 일자(오늘 일자를 나타내는 Date 클래스의 인스턴스)이고 두 번째 요소가 현재 시간(현재 시간을 나타내는 Time 클래스의 인스턴스)인 Array를 응답한다.
Date 클래스 프로토콜


따라서 아래와 같은 메시지를 전송할 수 있다.

표현식 결과
Date daysInYear: 1982 365
Date dayOfWeek: #Wednesday 3
Date nameOfMonth: 10 October
Date leapYear: 1972 1 (meaning it is a leap year)
Date daysInMonth: #February forYear:1972 29
Date daysInMonth: #Feb forYear: 1972 28


Date는 월 이름의 축약어에 익숙하다.


Date 클래스의 인스턴스를 생성하는 데에 이용할 수 있는 메시지는 네 가지가 있다. Smalltalk-80에서는 파일의 생성일자를 표시할 때 사용되는 Date today가 가장 흔하다.

인스턴스 생성
today 메시지가 전송된 일자를 나타내는 Date의 인스턴스를 응답한다.
fromDays: dayCount 1901년 1월 1일 이전 또는 이후부터(인자의 부호에 따라 결정) dayCount 일수에 해당하는 Date의 인스턴스를 응답한다.
newDay: day month: monthName year: yearInteger yearInteger 연도에서 monthName으로 명명된 월에 day 일수에 해당하는 Date의 인스턴스를 응답한다.
newDay: dayCount year: yearInteger yearInteger 연도가 시작된 이후 dayCount 일수에 해당하는 Date의 인스턴스를 응답한다.
Date 인스턴스 프로토콜


인스턴스 생성 메시지의 예를 네 가지 소개하겠다.

표현식 결과
Date today 3 February 1982
date fromDays: 200 20 July 1901
date newDay: 6 month: #Feb year: 82 6 February 1982
Date newDay: 3 year: 82 3 January 1982


Date의 인스턴스로 전송 가능한 메시지는 접근, 문의, 산술, 출력 메시지로 분류된다. 특정 일자에 대한 접근 및 문의는 아래로 구성된다.

  • 요일 색인, 월 색인 또는 연도
  • 다른 일자부터 경과한 초, 일 또는 개월 수
  • 일자의 월 또는 연도에 총 일수
  • 일자의 월 또는 연도에서 남은 총 일수
  • 일자의 주 또는 월 이름
  • 인스턴스 전에 특정 요일의 일자


Date 클래스의 프로토콜에서는 간단한 산술이 지원된다.

산술
addDays: dayCount 수신자 이후로 dayCount 일수에 해당하는 Date를 응답한다.
subtractDays: dayCount 수신자 이전에 dayCount 일수에 해당하는 Date를 응답한다.
subtractDate: aDate 수신자와 aDate 인자 사이의 일수를 나타내는 Integer를 응답한다.
Date 인스턴스 프로토콜


그러한 산술은 가령 도서관에서 책의 반납일이나 연체된 서적에 대한 벌금을 계산할 때 유용하다. dueDate가 도서관에서 책 반납일을 나타내는 Date의 인스턴스라고 가정해보면,

Date today subtractDate: dueDate


위는 대출자에게 대출이 허용되는 일수를 계산한다. 오늘 책을 빌려서 2주간 대여가 허용된다면,

Date today addDays: 14


위는 책의 반납일이 된다. 만일 사서가 크리스마스 16일 전에 직장을 그만두고 싶다면 마지막 근무일은 아래가 될 것이다.

(Date newDay: 25 month: #December year: 1982) subtractDays: 16


대출자에게 지급해야 하는 금액을 결정하기 위한 알고리즘은 먼저 반납일과 오늘 일자를 비교한 다음 반납일이 지나면 연체된 일수에 10 센트를 곱하여 부과하기로 결정한다.

Date today < dueDate
    ifTrue: [fine   0]
    ifFalse: [fine   0.10 * (Date today subtractDate: dueDate)]


Time 클래스

Time 클래스의 인스턴스는 요일중에 특정 초를 나타낸다. 하루는 자정부터 시작된다. Time은 Magnitude의 서브클래스다. Date 클래스와 마찬가지로 Time은 클래스 프로토콜에 명시된 일반적 문의 메시지에 응답할 수 있다.

일반적 문의
millisecondClockValue 밀리초 시계가 마지막으로 리셋되거나 0으로 돌아간 이후 초과된 시간의 밀리초수를 응답한다.
millisecondsToRun: timedBlock 값을 리턴하기 위해 걸리는 시간의 timedBlock 밀리초수를 응답한다.
timeWords 1901년 1월 1일부터 경과한 시간의 초수를 응답한다 (그리니치 표준시 기준). 응답은 4요소 ByteArray다 (ByteArray는 제 10장에서 설명).
totalSeconds 1901년 1월 1일부터 경과한 시간의 초수를 표준 시간대와 일광 절약 시간제에 조정하여 응답한다.
dateAndTimeNow 첫 번째 요소가 현재 일자이고 (오늘 일자를 나타내는 Date 클래스의 인스턴스) 두 번째 요소가 현재 시간인 (현재 시간을 나타내는 Time 클래스의 인스턴스) Array를 응답한다. 이 메시지를 Time으로 전송하면 Date로 전송한 결과와 동일하다.
Time 클래스 프로토콜


유일하게 불분명한 문의는 millisecondsToRun: timedBlock이다. 예를 들어,

Time millisecondsToRun: [Date today]


위에서 결과는 시스템이 오늘 일자를 계산하는 데에 소요된 시간의 밀리초가 된다. 이 메시지로 응답하는 데에 약간의 오버헤드가 발생하였고 시계의 해상도는 결과에 영향을 미치므로 주의가 깊은 프로그래머는 이 메시지에 적당한 인자와 연관된 머신 의존적인 불확실성을 결정해야 한다.


now 메시지를 Time으로 전송하면 해당 메서드가 시스템 시계로부터 현재 시간을 읽음으로써 Time의 새로운 인스턴스가 생성된다. 아니면 fromSeconds: secondCount 메시지를 전송하여 새로운 Time 인스턴스를 생성하는 방법도 있는데 여기서 secondCount는 자정 이후 경과한 초수를 나타낸다.

인스턴스 생성
now 메시지가 전송된 초수를 나타내는 Time의 인스턴스를 응답한다.
fromSeconds: secondCount 자정 이후 경과한 초수 secondCount에 해당하는 Time의 인스턴스를 응답한다.
Time 클래스 프로토콜


Time 클래스의 인스턴스에 대한 접근 프로토콜은 인스턴스가 나타내는 시간(hours), 분(minutes), 초(seconds) 수와 관련된 정보를 제공한다.


산술 또한 지원된다.

산술
addTime: timeAmount 수신자 이후의 timeAmount 인자에 해당하는 Time의 인스턴스를 응답한다.
subtractTime: timeAmount 수신자 이전의 timeAmount 인자에 해당하는 Time의 인스턴스를 응답한다.
Time 인스턴스 프로토콜


위에 제공된 메시지에서 인자들(timeAmount)은 Dates와 Times 둘 중 하나일 것이다. 이것이 가능하려면 시스템이 Date와 Time을 공통된 측정 단위로 변환할 수 있어야 하는데, 초로는 본래 변환이 가능하다. Time의 경우 변환은 자정 이후 경과한 초수이고, Date의 경우 1901년 1월 1일의 시간부터 수신자 일자에서 동일한 시간까지 경과한 초수가 변환된다. 이러한 메서드를 지원하기 위해 각 클래스의 인스턴스는 asSeconds라는 변환 메시지에 응답한다.

Time 인스턴스 프로토콜
asSeconds 수신자가 나타내는 자정 이후 경과한 초수를 응답한다.
Time 인스턴스 프로토콜


변환하기
asSeconds 1901년 1월 1일에 어떤 시간부터 수신자 일자에서 동일한 시간까지 초수를 응답한다.
Date 인스턴스 프로토콜


Time에 대한 산술은 Date에 대한 산술과 동일한 방식으로 사용할 수 있다. 한 사람이 특정 프로젝트에 작업할 때 소요되는 시간이 로그(log)되어 고객에게 시간당 요금을 부과할 수 있다고 가정해보자. 그리고 그 사람이 startTime부터 작업을 시작하여 현재 시간까지 계속한다고 가정하고, 전화가 울리면 고객은 오늘 부과된 금액을 알고 싶어한다고 치자. 현재 시간당 $5.00로 부과되는 청구서는 다음과 같으며,

(Time now subtractTime: startTime) hours * 5


추가 분(minutes)이나 초(second)는 모두 무시한다. 30분 이상부터 1시간 미만까지는 1시간으로 간주되어 다음과 같은 경우 추가로 $5.00가 부가된다.

(Time now subtractTime: startTime) minutes > 30


그렇다면 timeA에 로그하여 작업을 완료한 직원과 timeB 시간에 완료한 직원 중 누가 더 생산적일까? timeA < timeB 라는 조건에서는 첫 번째 사람이 더 생산적이라고 할 수 있다. 비교 프로토콜은 슈퍼클래스 Magnitude와 Object로부터 상속된다.


4일간 지속되는 경주에서 자동차의 시간을 계산하는 것처럼 며칠에 걸친 시간을 계산한다고 가정해보자. 경주 첫째 날이 startDate 요일의 startTime에서 시작되면, 현재 결승선에 도착하는 자동차의 시간은 아래와 같이 계산될 것이다.


시작 시간을 6:00 a.m.,

startTime  Time fromSeconds: (60*60,6).


일자는 1982년 2월 2일로 가정하자.

startDate   Date newDay: 2 month: #Feb year: 82.


현재 요일이 시작되는 시간까지 경과된 시간은 다음과 같다.

todayStart   (((Time fromSeconds: 0) addTime: Date today)
    subtractTime: startDate)
        subtractTime: startTime


즉 1901년 1월 1일부터 오늘이 시작되는 순간까지 초수를 모두 더한 후 1901년 1월 1일부터 경주의 시작일이 시작되는 순간까지 초수를 뺀다. 이는 경과된 일수의 초를 더하는 것과 동일하지만 이 방법을 이용할 경우 모든 변환은 프로그래머가 책임져야 한다.

(Date today subtractDate: startDate), 24*60,60)


현재 시간을 더하면 자동차에 경과한 경주 시간이 나온다.

todayStart addTime: Time now


Character 클래스

Character 클래스는 세 번째로 살펴보게 될 Magnitude 의 서브클래스다. Character 클래스는 한 문자가 알파벳상으로 다른 문자보다 앞서는지 뒤에 따라오는지 등의 문의를 하는 데에 정렬된 시퀀스를 형성하므로 Magnitude 유형에 해당한다. 시스템에는 Character 클래스의 인스턴스가 256개 존재한다. 각 인스턴스는 확장 ASCII 문자 집합의 코드와 연관된다.


Characters는 앞에 달러 부호($)를 붙여 문자로 표현 가능하므로 $A는 대문자 “A”를 표현하는 Character다. Character 클래스의 인스턴스를 생성하기 위한 프로토콜은 Character 객체로의 메시지 집합인 클래스 프로토콜을 제공하는데, 이는 출력 시 구별이 쉽지 않은 backspace, cr, esc, newPage(즉, 폼 피드), space, tab과 같은 문자로 접근하기 위한 어휘를 제공한다.

인스턴스 생성
value: anInteger anInteger 인자를 값으로 가진 Character의 인스턴스를 응답한다. 값은 ASCII 문자 집합의 요소와 연관된다. 예를 들어 Character value: 65는 대문자 "A" 다.
digitValue: anInteger anInteger 인자를 디지트(digit) 값으로 가진 Character의 인스턴스를 응답한다. 가령 인자가 9라면 $9를 응답하고, 0이라면 0$를, 35에는 $Z를 응답한다. 해당 메서드는 숫자를 문자열로 파싱할 때 유용하다. 보통 $F까지 Chacracters만 유용하다 (16진수).
Character 클래스 프로토콜


Character 객체로 전송되는 메시지 집합인 Class 프로토콜은 출력 시 구별하기가 쉽지 않은 문자, 즉 backspace, cr, esc, newPage(다시 말해 feed 폼), space, tab과 같은 문자로 접근하기 위한 어휘를 제공한다.


Character의 인스턴스로 전송되는 메시지는 ASCII 값과 인스턴스의 디지트 값으로 접근 및 문자 타입의 검사를 지원한다. Character는 하나의 상태만 가지는데, 절대 변경할 수 없는 값이 바로 그것이다. 내부 상태를 변경할 수 없는 객체를 "immutable objects(불변 객체)"라고 부른다. 이는 우선 생성되고 나면 소멸되었다가 필요할 때 다시 생성되는 것이 불가능하다는 의미다. 대신 시스템이 초기화될 때 Character의 256개 인스턴스가 생성되었다가 시스템에 계속 유지된다. 코드가 0부터 255까지 사이에 있는 새로운 Character를 요청할 때마다 이미 존재하는 Character로 참조가 제공된다. 이렇게 256개의 Character는 모두 유일하다. Characters 외에도 Smalltalk-80 시스템은 SmallIntegers와 Symbols를 불변 객체로 포함한다.

접근하기
asciiValue 수신자에 대한 ASCII 인코딩에 상응하는 숫자를 응답한다.
digitValue 수신자가 나타내는 수치 기수에 상응하는 숫자를 응답한다 (인스턴스 생성 메시지 digitValue: 를 참고).
검사하기
isAlphaNumeric 수신자가 문자 또는 디지트일 경우 true를 응답한다.
isDigit 수신자가 디지트인지 여부를 응답한다.
isLetter 수신자가 문자인지 여부를 응답한다.
isLowercase 수신자가 소문자인지 여부를 응답한다.
isUppercase 수신자가 대문자인지 여부를 응답한다.
isSeparator 수신자가 표현식 구문에서 구분 문자 space, cr, tab, line feed, form feed 중 하나인지 응답한다.
isVowel 수신자가 대문자 또는 소문자로 된 모음 a, e, i, o, u 중에서 하나인지 응답한다.
Character 인스턴스 프로토콜


인스턴스 프로토콜은 문자를 대문자나 소문자로의 변환과 (asLowercase와 asUppercase) 심볼(asSymbol)로의 변환도 제공한다.


간단한 알파벳 비교를 통해 Character의 인스턴스에 대한 프로토콜의 비교 예를 보일 수 있다. 문자에서 하나의 문자열이 주소록에 포함된 다른 문자열보다 앞서는지 알고 싶다고 가정하자. Strings는 인자를 색인으로 가진 요소를 검색하기 위해 at: 메시지로 응답하고, Strings의 요소는 Characters에 해당한다. 따라서 'abc' at:2 는 $b가 된다. 아래에서는 메시지 선택자가 min: 인 메서드를 String 클래스에서 명시한다고 가정하자. 메서드는 String을 리턴하는데, 이는 min: 메시지의 수신자와 그 인자 중 알파벳 상으로 먼저 오는 것에 해당한다.

min: aString
    1 to: self size do:
        [ :index |
            (index > aString size) ifTrue: [ aString].
            (self at: index) > (aString at: index) ifTrue: [ aString].
            (self at: index) < (aString at: index) ifTrue: [ self]].
     self


알고리즘은 두 개의 문으로 구성된다. 하나는 수신자의 각 요소마다 반복에 해당하는 문이다. 반복은 (1) aString 인자가 더 이상 수신자에서 다음 문자와 비교할 문자를 갖고 있지 않을 때 (예: index > aString size), (2) self에서 다음 문자가 aSTring에서 다음 문자보다 뒤에 올 때 (예: (self at: index) > (aString at: index)), 또는 (3) self에서 다음 문자가 aSTring에서 다음 문자보다 앞에 올 때에 중단된다. (1)의 경우, 'abcd'와 'ac'의 비교하여 index = 4가 되면 종료되도록 할 경우 'abc'가 알파벳 순으로 먼저 온다는 것이 답이 된다. (2)의 경우 'abde'와 'abce'를 비교한다고 치자. index = 3의 경우 $d > $c 가 true가 되고 'abce'가 답이 된다. (3)의 경우 index = 1이 되면 종료되도록 'az'와 'by'를 비교하면 'az'가 답이 된다. 수신자가 인자보다 적은 수의 문자를 갖고 있다면, 수신자가 인자의 첫 하위문자열이라 하더라도 첫 번째 문이 완료되어 두 번째 문이 평가되므로 수신자를 결과로 낳는다. 'abc'와 'abcde'의 비교가 이러한 예에 해당한다.


문자에서 산술은 지원되지 않음을 주목한다. 예를 들어, 아래와 같은 표현식은 올바르지 못하다.

a   $A + 1


Character는 + 메시지를 이해하지 못하므로 오류가 발생한다.


Notes