Smalltalk80LanguageImplementation:Chapter 07
- Chapter 7 Linear Measures
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
The Smalltalk-80 system provides several classes representing objects that measure something with linear ordering. Real world examples of such measurable quantities are (1) temporal quantities such as dates and time, (2) spatial quantities such as distance, and (3) numerical quantities such as reals and rationals.
Class Magnitude
IS one number less than another number? Does one date come after another date? Does one time precede another time? Does a character come after another one in the alphabet? Is one distance the same or less than another distance?
The common protocol for answering these queries is provided in the class Magnitude. Magnitude provides the protocol for objects that have the ability to be compared along a linear dimension. Subclasses of class Magnitude include Date, Time, and Number. Classes Character (an element of a string) and LookupKey (a key in a dictionary association) are also implemented as subclasses of class Magnitude. Character is interesting as an example of immutable objects in the system and so is introduced in this chapter; LookupKey is less interesting and is deferred until needed in the chapter on collections. A class Distance is not provided in the actual Smalltalk-80 system.
comparing | |
< aMagnitude | Answer whether the receiver is less than the argument. |
< = aMagnitude | Answer whether the receiver is less than or equal to the argument. |
> aMagnitude | Answer whether the receiver is greater than the argument. |
> = aMagnitude | Answer whether the receiver is greater than or equal to the argument. |
between: min and: max | Answer whether the receiver is greater than or equal to the argument, rain, and less than or equal to the argument, max. |
Magnitude instance protocol |
Although Magnitude inherits from its superclass, Object, the message = for comparing the equality of two quantifiable objects, every kind of Magnitude must redefine this message. The method associated with = in class Magnitude is
self subclassResponsibility
If a subclass of Magnitude does not implement = , then an attempt to send the message to an instance of the subclass results in the special error message that a subclass should have implemented the message,as specified in its superclass.
An instance of a kind of Magnitude can also respond to messages that determine which of two objects that can be linearly measured is the larger or the smaller.
testing | |
min: aMagnitude | Answer the receiver or the argument, whichever has the lesser magnitude. |
max: aMagnitude | Answer the receiver or the argument, whichever has the greater magnitude. |
Magnitude instance protocol |
Note that protocol for the equality comparisons = =, ~ = , and ~~ is inherited from class Object. Using Integers as the example kinds of Magnitudes, we have
expression | result |
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 |
The programmer does not create instances of Magnitude, but only of its subclasses. This is due to the fact that Magnitude is not able to implement all of the messages it specifies, indeed, that it implements one or more of these messages by the expression self subclassResponsibility.
Class Date
Now that we have defined the general protocol of Magnitudes, it is possible to add additional protocol that supports arithmetic and inquiries about specific linear measurements. The first refinement we will examine is the subclass Date.
An instance of Date represents a specific day since the start of the Julian calendar. A day exists in a particular month and year. Class Date knows about some obvious information: (1) there are seven days in a week, each day having a symbolic name and an index 1, 2, ..., or 7; (2) there are 12 months in a year, each having a symbolic name and an index, 1, 2, ..., or 12; (3) months have 28, 29, 30, or 31 days; and (4) a particular year might be a leap year.
Protocol provided for the object, Date, supports inquiries about Dates in general as well as about a specific Date. Both Date and Time provide interesting examples of classes in the system for which special knowledge is attributed to and accessible from the class itself, rather than from its instances. This "class protocol" is specified in the metaclass of the class. Let's first look at the class protocol of Date supporting general inquiries.
general inquiries | |
dayOfWeek: dayName | Answer the index in a week, 1, 2 . . . . or 7, of the day named as the argument, dayName. |
nameOfDay: dayIndex | Answer a Symbol that represents the name of the day whose index is the argument, dayIndex, where 1 is Monday, 2, is Tuesday, and so on. |
indexOfMonth: monthName | Answer the index in a year, 1, 2, ..., or 12, of the month named as the argument, monthName. |
nameOfMonth: monthIndex | Answer a Symbol that represents the name of the month whose index is the argument, monthIndex, where 1 is January, 2, is February, and so on. |
daysInMonth: monthName forYear: yearInteger | Answer the number of days in the month whose name is monthName in the year yearInteger (the year must be known in order to account for a leap year). |
daysInYear: yearInteger | Answer the number of days in the year, yearInteger. |
leapYear: yearInteger | Answer 1 if the year yearInteger is a leap year; answer 0 otherwise. |
dateAndTimeNow | Answer an Array whose first element is the current date (an instance of class Date representing today's date) and whose second element is the current time (an instance of class Time representing the time right now). |
Date class protocol |
Thus we can send the following messages.
expression | result |
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 is familar with the common abbreviations for names of months.
There are four messages that can be used to create an instance of class Date. The one commonly used in the Smalltalk-80 system, notably for marking the creation date of a file, is Date today.
instance creation | |
today | Answer an instance of Date representing the day the message is sent. |
fromDays: dayCount | Answer an instance of Date that is dayCount number of days before or after January 1, 1901 (depending on the sign of the argument). |
newDay: day month: monthName year: yearInteger | Answer an instance of Date that is day number of days into the month named monthName in the year yearInteger. |
newDay: dayCount year: yearInteger | Answer an instance of Date that is dayCount number of days after the beginning of the year yearInteger. |
Date instance protocol |
Four examples of instance creation messages are
expression | result |
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 |
Messages that can be sent to an instance of Date are categorized as accessing, inquiries, arithmetic , and printing messages. Accessing and inquiries about a particular day consist of
- the day index, month index, or year
- the number of seconds, days, or months since some other date
- the total days in the date's month or year
- the days left in the date's month or year
- the first day of the date's month
- the name of the date's weekday or month
- the date of a particular weekday previous to the instance
Simple arithmetic is supported in the protocol of class Date.
arithmetic | |
addDays: dayCount | Answer a Date that is dayCount number of days after the receiver. |
subtractDays: dayCount | Answer a Date that is dayCount number of days before the receiver. |
subtractDate: aDate | Answer an Integer that represents the number of days between the receiver and the argument, aDate. |
Date instance protocol |
Such arithmetic is useful, for example, in order to compute due dates for books in a library or fines for late books. Suppose dueDate is an instance of Date denoting the day a book was supposed to be returned to the library. Then
Date today subtractDate: dueDate
computes the number of days for which the borrower should be fined. If a book is being borrowed today and it can be kept out for two weeks, then
Date today addDays: 14
is the due date for the book. If the librarian wants to quit work 16 days before Christmas day, then the date of the last day at work is
(Date newDay: 25 month: #December year: 1982) subtractDays: 16
An algorithm to determine the fine a borrower must pay might first compare today's date with the due date and then, if the due date has past, determine the fine as a 10-cent multiple of the number of days overdue.
Date today < dueDate
ifTrue: [fine ← 0]
ifFalse: [fine ← 0.10 * (Date today subtractDate: dueDate)]
Class Time
An instance of class Time represents a particular second in a day. Days start at midnight. Time is a subclass of Magnitude. Like class Date, Time can respond to general inquiry messages that are specified in the class protocol.
general inquiries | |
millisecondClockValue | Answer the number of milliseconds since the millisecond clock was last reset or rolled over to 0 |
millisecondsToRun: timedBlock | Answer the number of milliseconds timedBlock takes to return its value. |
timeWords | Answer the seconds (in Greenwich Mean Time) since Jan. 1, 1901. The answer is a four-element ByteArray (ByteArray is described in Chapter 10). |
totalSeconds | Answer the total seconds from Jan. 1, 1901, corrected for time zone and daylight savings time. |
dateAndTimeNow | Answer an Array whose first element is the current date (an instance of class Date that represents today's date) and whose second element is the current time (an instance of class Time that represents the time right now). The result of sending this message to Time is identical to the result of sending it to Date. |
Time class protocol |
The only non-obvious inquiry is millisecondsToRun: timedBlock. An example is
Time millisecondsToRun: [Date today]
where the result is the number of milliseconds it took the system to compute today's date. Because there is some overhead in responding to this message, and because the resolution of the clock affects the result, the careful programmer should determine the machine-dependent uncertainties associated with selecting reasonable arguments to this message.
A new instance of Time can be created by sending Time the message now; the corresponding method reads the current time from a system clock. Alternatively, an instance of Time can be created by sending the message fromSeconds: secondCount, where SecondCount is the number of seconds since midnight.
instance creation | |
now | Answer an instance of Time representing the second the message is sent. |
fromSeconds: secondCount | Answer an instance of Time that is secondCount number of seconds since midnight. |
Time class protocol |
Accessing protocol for instances of class Time provide information as to the number of hours (hours), minutes (minutes) and seconds (seconds) that the instance represents.
Arithmetic is also supported.
arithmetic | |
addTime: timeAmount | Answer an instance of Time that is the argument, timeAmount, after the receiver. |
subtractTime: timeAmount | Answer an instance of Time that is the argument, timeAmount, before the receiver. |
Time instance protocol |
In the messages given above, the arguments (timeAmount) may be either Dates or Times. For this to be possible, the system must be able to convert a Date and a Time to a common unit of measurement ; it converts them to seconds. In the case of Time, the conversion is to the number of seconds since midnight; in the case of Date, the conversion is to the number of seconds between a time on January 1, 1901, and the same time in the receiver's day. To support these methods, instances of each class respond to the conversion message asSeconds .
Time instance protocol | |
asSeconds | Answer the number of seconds since midnight that the receiver represents. |
Time instance protocol |
converting | |
asSeconds | Answer the number of seconds between a time on January 1, 1901, and the same time in the receiver's day. |
Date instance protocol |
Arithmetic for Time can be used in ways analogous to that for Date. Suppose the amount of time a person spends working on a particular project is to be logged so that a customer can be charged an hourly fee. Suppose the person started work at startTime and worked continuously during the day until right now; the phone rings and the customer wants to know today's charges. At that moment, the bill at $5.00 an hour is
(Time now subtractTime: startTime) hours * 5
ignoring any additional minutes or seconds. If a charge for any fraction of an hour over 30 minutes is to be charged as a full hour then an additional $5.00 is added if
(Time now subtractTime: startTime) minutes > 30
Who is more productive, the worker who finished the job with time logged at timeA or the worker with time timeB? The answer is the first worker if timeA < timeB. Comparing protocol is inherited from the superclasses Magnitude and Object.
Suppose times are computed across days, for example, in computing the time of a car in a four-day rally. If the first day of the rally started at startTime on day startDate, then the time for a car arriving at the finish line right now is computed as follows.
Let the start time be 6:00 a.m.
startTime ← Time fromSeconds: (60*60,6).
on February 2, 1982
startDate ← Date newDay: 2 month: #Feb year: 82.
The time that has passed up to the start of the current day is
todayStart ← (((Time fromSeconds: 0) addTime: Date today)
subtractTime: startDate)
subtractTime: startTime
That is, add all the seconds since Jan. 1, 1901, up to the start of today and then subtract all the seconds since Jan. 1, 1901, up to the start of the start date. This is equivalent to adding the number of seconds in the number of elapsed days, but then the programmer would have to do all the conversions.
(Date today subtractDate: startDate), 24*60,60)
By adding the current time, we have the elapsed rally time for the car.
todayStart addTime: Time now
Class Character
Class Character is the third subclass of class Magnitude we shall examine. It is a kind of Magnitude because instances of class Character form an ordered sequence about which we can make inquiries such as whether one character precedes ( < ) or succeeds ( > ) another character alphabetically. There are 256 instances of class Character in the system. Each one is associated with a code in an extended ASCII character set.
Characters can be expressed literally by preceding the alphabetic character by a dollar sign ($); thus, $A is the Character representing the capital letter "A". Protocol for creating instances of class Character consists of
instance creation | |
value: anInteger | Answer" an instance of Character whose value is the argument, anInteger. The value is associated with an element of the ASCII character set. For example, Character value: 65 is a capital "A". |
digitValue: anInteger | Answer an instance of Character whose digit value is the argument, anInteger. For example, answer $9 if the argument is 9; answer $0 for 0; answer $A for 10, and $Z for 35. This method is useful in parsing numbers into strings. Typically, only Characters up to $F are useful (for base 16 numbers). |
Character class protocol |
Class protocol, that is, the set of messages to the object Character, provides a vocabulary for accessing characters that are not easy to distinguish when printed: backspace, cr, esc, newPage (that is, form feed), space, and tab.
Messages to instances of Character support accessing the ASCII value and the digit value of the instance and testing the type of character. The only state of a Character is its value which can never change. Objects that can not change their internal state are called immutable objects. This means that, once created, they are not destroyed and then recreated when they are needed again. Rather, the 256 instances of Character are created at the time the system is initialized and remain in the system. Whenever a new Character whose code is between 0 and 255 is requested, a reference is provided to an already existing Character. In this way the 256 Characters are unique. Besides Characters, the Smalltalk-80 system includes SmallIntegers and Symbols as immutable objects.
accessing | |
asciiValue | Answer the number corresponding to the ASCII encoding for the receiver. |
digitValue | Answer the number corresponding to the numerical radix represented by the receiver (see the instance creation message digitValue: for the correspondences). |
testing | |
isAlphaNumeric | Answer true if the receiver is a letter or a digit. |
isDigit | Answer whether the receiver is a digit. |
isLetter | Answer whether the receiver is a letter. |
isLowercase | Answer whether the receiver is a lowercase letter. |
isUppercase | Answer whether the receiver is an uppercase letter. |
isSeparator | Answer whether the receiver is one of the separator characters in the expression syntax: space, cr, tab, line feed, or form feed. |
isVowel | Answer whether the receiver is one of the vowels: a, e, i, o, or u, in upper or lowercase. |
Character instance protocol |
Instance protocol also provides conversion of a character into upper or lowercase ( asLowercase and asUppercase) and into a symbol (asSymbol).
A simple alphabetic comparison demonstrates the use of comparing protocol for instances of Character. Suppose we wish to know if one string of characters precedes another string in the telephone book. Strings respond to the message at: to retrieve the element whose index is the argument; elements of Strings are Characters. Thus 'abc' at: 2 is $b. In the following we assume we are specifying a method in class String whose message selector is min:. The method returns a String, either the receiver of the message rain: or its argument, whichever is collated first alphabetically.
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
The algorithm consists of two statements. The first is an iteration over each element of the receiver. The iteration stops when either (1) the argument, aString, no longer has a character with which to compare the next character in the receiver (i.e., index > aString size); (2) the next character in self comes after the next character in aString (i.e., (self at: index) > (aString at: index)); or (3) the next character in self comes before the next character in aString. As an example of (1), take the comparison of 'abcd' and 'abc' which terminates when index = 4; the answer is that 'abc' is first alphabetically. For (2), suppose we compare 'abde' with 'abce'. When index = 3, $d > $c is true; the answer is 'abce'. For (3), compare 'az' with 'by' which terminates when index -- 1; the answer is 'az'. In the case that the receiver has fewer characters than the argument, even when the receiver is the initial substring of the argument, the first statement will complete and the second statement is evaluated; the result is the receiver. An example is the comparison of 'abc' and 'abcd'.
Note that arithmetic on characters is not supported. For example, the following expression is incorrect.
a ← $A + 1
The error occurs because a Character does not understand the message +.