CSE 341 -- Notes on Smalltalk


The Learning Research Group at Xerox Palo Alto Research Center (PARC) developed Smalltalk as a language for the Dynabook, during the 70s and early 80s. The Dynabook was a yet-to-be-developed "laptop of the future." Alan Kay, Dan Ingalls, Adele Goldberg were key players in the development of Smalltalk. They selected simulation as the metaphor for programming the Dynabook. Smalltalk was influenced by Lisp, Simula, Ivan Sutherland's Sketchpad.

Characteristics of Smalltalk-72:

Characteristics of Smalltalk-80: We're using the Squeak implementation of Smalltalk. The language itself has been quite stable since 1980, but there are constantly new packages and "goodies".

Smalltalk influenced the development of other object-oriented languages, including Java, C++, Objective C, CLOS, and others.

Language Overview

We'll now give a quick overview of the language, via a few small examples. Basic concepts: Syntax: Precedence:

Note that we will very frequently be composing messages -- for example

Time now hours + 1
First 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).

Example 1: Stack

Note: in Squeak, assignment is normally written as a left arrow (typed using _ ). However := will work as well.
Object subclass: #Stack
    instanceVariableNames: 'store top '
    classVariableNames: ''
    poolDictionaries: ''
    category: 'Stacks'

push: item
   top := top+1.
   store at: top put: item

   | 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

   ^ top=0

Classes, Objects, and Inheritance

Now let's look at classes, objects, and inheritance more carefully.

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'


addPassenger: aPerson
   passengers add: aPerson

removePassenger: aPerson
   passengers remove: aPerson

   passengers := OrderedCollection new.


Smalltalk allows us to define classes that are subclasses of other classes. In the above example, Vehicle is a subclass of the class Object. Because of this subclass relationship, we say that Vehicle inherits from Object. Every instance of Vehicle, therefore, responds to the same set of messages defined by Object, in addition to the messages defined by the class Vehicle. An instance of a class has access to all of the instance variables declared in its superclass. Subclasses of a given class may add instance variables, and add or override methods inherited from that class. Now, we'll create a subclass of Vehicle:
Vehicle subclass: #Bus
    instanceVariables: 'route'
    classVariableNames: ''
    poolDictionaries: ''
    category: Tests'

route: r
   route := r


   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.

Inheritance and method lookup

Let's say we evaluate the following piece of Smalltalk code:
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.

Introduction to the Class Hierarchy

Now that we know a little about inheritance, we can introduce Smalltalk's rich class hierarchy. Here is a simplified picture: At the top of the hierarchy, is of course, Object. All classes are subclasses of the Object class. A few important methods defined by Object are:
  1. Equality Tests: = and ~= (these are like Scheme's EQUAL)
  2. Identity Tests: == and ~~ (these are like Scheme's EQ)
  3. Copying: copy
  4. Printing: printString and printOn: aStream
Numbers pretty much respond to messages as we'd expect (+, -, *, /, >, <, etc.). There are a few important methods, especially regarding division, remaindering, and modulus, which you should be aware of from the reading. We never have to explicitly convert between the number classes (conversions are done automatically). For instance if we say: 3.4 + 3, the result will be 6.4 (a Float). Numbers are converted to more general classes as necessary (Floats are more general than Fractions are more general than Integers).


The fields of an instance are not visible outside of the object. However, all methods are visible. The category "private" is just documentation -- no runtime enforcement. Minimal namespace management (global variables, class variables, pool variables).

Control Abstractions

All control structures are done with blocks (closures) and message passing. This is characteristic of pure object-oriented languages, but not of hybrid languages. All of the basic control structures are implemented as methods in the classes True or False. To understand the implementations of the classes True and False, we first need to understand how blocks work.


A block is Smalltalk's way of defining a lexical closure (like a lambda expression in Scheme or an anonymous function in ML).
| 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.

Boolean "operators"

Here are the method definitions which implement and, or, and not for True objects.

   printOn: stream
      stream nextPutAll: 'true'

   & b   "evaluating and"
      ^ b

   | b   "evaluating or"
      ^ true

      ^ 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...

   printOn: stream
      stream nextPutAll: false'

   & b   "evaluating and"
      ^ false

   "etc etc etc"

Examples of Boolean expressions:

  (3=4) & (2>1)

  (3=4) | (2>1)

  (3=2) not

  true & false not

  (3=4) and: [(1/0) = 8]


Conditionals are also implemented as methods on True or False:

   ifTrue: block
      ^ block value

   ifFalse: block
      ^ nil

   ifTrue: tBlock ifFalse: fBlock
      ^ tBlock value


   ifTrue: block
      ^ nil

   ifFalse: block
      ^ block value

   ifTrue: tBlock ifFalse: fBlock
      ^ fBlock value

Examples of Conditionals:

  3=4 ifTrue: [x := 10].

  x=y ifTrue: [x := 8] ifFalse: [x := 9].

  x := x=y ifTrue: [8] ifFalse: [9].

Simple Iteration

Blocks and the boolean classes are also used to implement iterators.
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):


   whileTrue: otherBlock
      self value ifTrue: [otherBlock value.  self whileTrue: otherBlock].

   whileFalse: otherBlock
      self value ifFalse: [otherBlock value.  self whileTrue: otherBlock].

Introduction to the Collection Hierarchy

The collection hierarchy is responsible for much of Smalltalk's richness as a language and programming environment. A collection is simply an abstraction for a group of objects. There is an important protocol (interface) shared by all collections in Smalltalk, which we introduce here. First, here are the basic utility functions defined on Collections:

   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"

	"answer whether receiver is empty"

        "answer the number of elements in the receiver"

Control Abstractions for Collections


   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"