Characteristics of Smalltalk-72:
fred move up x inches
Smalltalk influenced the development of other object-oriented languages, including Java, C++, Objective C, CLOS, and others.
Date today Time now hours Array new someCollection copy
Array new: 10 someArray at: 1 put: 54 anArray at: 1
5 * 9 3 + 2 * 5
Note that we will very frequently be composing messages -- for example
Time now hours + 1First sends the message
now to the class Time,
which returns the current time (an instance of Time). We then send this
object the message hours, which returns an integer. Then we
send the message + with the argument 1 to this integer,
returning another integer (which will be the current hour plus 1).
Object subclass: #Stack
instanceVariableNames: 'store top '
classVariableNames: ''
poolDictionaries: ''
category: 'Stacks'
push: item
top := top+1.
store at: top put: item
pop
| item |
item := store at: top.
top := top-1.
^ item
setsize: n
store := Array new: n.
top := 0.
Adding error checking and growing:
push: item
| oldStore |
top := top+1.
top > store size ifTrue:
"store is about to overflow. make a new array twice as big, and
copy the old values into it"
[oldStore := store.
store := Array new: 2 * oldStore size.
1 to: oldStore size do:
[:k | store at: k put: (oldStore at: k)]].
store at: top put: item
pop | item |
self isEmpty ifTrue: [self error: 'trying to pop an empty stack'].
item := store at: top.
top := top-1.
^ item
isEmpty
^ top=0
In Smalltalk, everything is an object, and classes act as descriptions of objects. Classes are used to define the set of messages an instance of that class responds to, as well as the variables contained in every instance of that class. In the below example, we define a class Vehicle. Instances of this class have one instance variables, called passengers. Instances also respond to four messages.
Object subclass: #Vehicle
instanceVariables: 'passengers '
classVariableNames: ''
poolDictionaries: ''
category: 'Tests'
passengers
^passengers
addPassenger: aPerson
passengers add: aPerson
removePassenger: aPerson
passengers remove: aPerson
init
passengers := OrderedCollection new.
Vehicle subclass: #Bus
instanceVariables: 'route'
classVariableNames: ''
poolDictionaries: ''
category: Tests'
route: r
route := r
route
^route
init
super init. "make sure inherited variables are initialized"
self route: 0 "could also just say route := 0"
Instances of the class Bus understand the above three methods, in
addition to those inherited from Vehicle. Note that Bus overrides the
init method. The above example also introduces two important
pseudo variables, self and super. Self is used when an
object wishes to refer to itself, and super is used to refer to the
superclass of the object.
B := Bus new. B init. B addPassenger: P.Remember that the new method asks the class Bus (which is an object) to give us an instance of the Bus class, so B is an instance of Bus. What happens when B is sent the init message? Method lookup in Smalltalk proceeds as follows: When a message is sent, methods in the receiver's class are searched for a matching method. If no match is found, the superclass is searched, and so on up the superclass chain. This means we find the init defined in Bus. We then send super the message init. This directs Smalltalk to begin the lookup in the superclass of the class containing the method in which super is used (this is not always the same as the class of class of the receiver!) This causes the init defined in Vehicle to be executed. Next, we send self the message route:, which means that the receiver (B) is sent the route: message. A message sent to self always causes the method lookup to begin at the instance of the object, regardless of where self is being referenced.
| c1 c2 x | x := 0. c1 := [ x := x+1 ]. "c1 is a block" c2 := [ :i | x := x+i ]. "c2 is a block w/ one parm" c1 value. "evaluate c1" c2 value: 20. "evaluate c2, with the argument 20" " now x equals 21... "We use square brackets to define a block. The names before the | in the block are parameters to the block (they must start with a colon). We can send a block the value message to force it to evaluate itself.
True
printOn: stream
stream nextPutAll: 'true'
& b "evaluating and"
^ b
| b "evaluating or"
^ true
not
^ false
and: block "short-circuit and"
^ block value
or: block "short-circuit or"
^ true
Given the above definitions, you can imagine what the parallel methods
for False objects look like...
False
printOn: stream
stream nextPutAll: false'
& b "evaluating and"
^ false
"etc etc etc"
(3=4) & (2>1) (3=4) | (2>1) (3=2) not true & false not (3=4) and: [(1/0) = 8]
True
ifTrue: block
^ block value
ifFalse: block
^ nil
ifTrue: tBlock ifFalse: fBlock
^ tBlock value
False
ifTrue: block
^ nil
ifFalse: block
^ block value
ifTrue: tBlock ifFalse: fBlock
^ fBlock value
3=4 ifTrue: [x := 10]. x=y ifTrue: [x := 8] ifFalse: [x := 9]. x := x=y ifTrue: [8] ifFalse: [9].
a := 1. [a < 10] whileTrue: [Transcript show: a. a := a+1]. a := 1. [a > 10] whileFalse: [Transcript show: a. a := a+1]. 1 to: 10 do: [:x | Transcript show: x].to: is a message understood by numbers. It creates an Interval (which is essentially a collection of numbers), which can then be iterated over, by the general purpose iterator do:. We'll see more examples of do: later, when we talk about the Collection hierarchy.
How to define whileTrue (not really done this way, though):
Block
whileTrue: otherBlock
self value ifTrue: [otherBlock value. self whileTrue: otherBlock].
whileFalse: otherBlock
self value ifFalse: [otherBlock value. self whileTrue: otherBlock].
Collection
add: newObject
"add newObject to the receiver"
addAll: aCollection
"add all objects in collection to the receiver"
remove: oldObject
"remove oldObject from the receiver, report error if not found"
remove: oldObject ifAbsent: aBlock
"remove oldObject from receiver, evaluate aBlock if not found"
removeAll: aCollection
"remove all elements in aCollection from receiver"
isEmpty
"answer whether receiver is empty"
size
"answer the number of elements in the receiver"
Collection
do: aBlock
"evaluate aBlock for each element"
collect: aBlock
"like Scheme map -- make a new collection by applying aBlock to each
element and collecting the results"
select: aBlock
"answer a new collection of the elements that pass the test"
reject: aBlock
"answer a new collection of the elements that fail the test"
inject: startValue into: binaryBlock
"like reduce in Scheme"
detect: aBlock
"like find-if in Scheme, answer the first element that passes the test"
Here are examples of how we might implement some of these methods: (They
are implemented in Collection, which is an abstract class, and inherited by
concrete classes such as OrderedCollection).
collect: aBlock
| newCollection |
"make a new collection of the same class as me"
newCollection := self class new.
self do: [:x | newCollection add: (aBlock value: x)].
^ newCollection
select: aBlock
| newCollection |
"make a new collection of the same class as me"
newCollection := self class new.
self do: [:x | (aBlock value: x) ifTrue: [newCollection add: x]].
^ newCollection
inject: startValue into: binaryBlock
| result |
result := startValue.
self do: [:x | result := binaryBlock value: result value: x].
^ result
a := #(3 4 5 6). "an array literal"
"this is the same as
a := Array new: 4.
a at: 1 put: 3.
a at: 2 put: 4. etc. "
a collect: [:j | j+10] "this returns (13 14 15 16)"
a select: [:j | j>4] "this returns (5 6)"
a inject: 0 into: [:a :b | a+b] "this returns 18"