We declared types when working with cellular automata
We have already started defining our own types that allow us to abstract what the computer is doing on a lower level. Recall from our work in Chapter 3 that when we worked with cellular automata like the Game of Life or Langton’s loops (replicated below), we thought of a cellular automaton in two ways.
- As a “game board”, an abstraction that is helpful for us humans.
- As a two-dimensional array stored within a computer of either boolean values (for two-state automata like the Game of Life) or integers (for multi-state automata like Langton’s loops).


Abstracting shapes
To help us transition toward object-oriented programming, we will imagine that we are producing an early version of an application for making slide decks like PowerPoint, and we want to find a simple abstract representation of different shapes such as rectangles or circles (shown below).

Exercise: What is the smallest amount of information that your computer needs to represent a rectangle in 2-D space? What is the smallest amount of information required to represent a circle? (Don’t think just yet about the color of the circle or the thickness of its boundary, but rather only its position.)
Our old friend Euclid, who gave us the world’s first nontrivial algorithm for computing a GCD in Chapter 0, proved much of the foundation of geometry in his Elements using only a compass and a straightedge. And you may or may not recall from the horrors of your geometry class that we can construct any circle that we like using a compass, which fixes the center of the circle, and draws the arc of all points that are a fixed distance from this center. In other words, the circle is defined as the set of all points that are a fixed distance from a given center.
Because of this definition, we could imagine defining our own Circle
type constituting just three pieces of information, all of which can be represented as float
(i.e., floating-point number) variables: the x-coordinate of the circle’s center, the y-coordinate of the circle’s center, and the circle’s radius.
We could therefore represent a Circle
object as an array of floating-point numbers of length 3, but this approach presents a couple of issues. First, the order that the circle’s three defining attributes should take within the array representing that circle is unclear. Furthermore, once we establish that order, we will need to be consistent with it throughout our code, and if we ever access c[0]
for a given Circle
variable c
, then the user must remember which attribute c[0]
represents.
Second, storing the attributes of a Circle
within an array is a brittle design choice. When we later update our software application and need to add an additional attribute to a Circle
, say its fill color, or its edge thickness, then we will need to greatly change our implementation by expanding the length of the array. And if the eventual attributes of a Circle
are not all floating-point numbers, then we will have an annoying array whose values have differing types.
Instead, we will think of a Circle
as an object, where each instance of the object has three variables associated with it that we call fields:
x1
: the x-coordinate of the circle’s center;y1
: the y-coordinate of the circle’s center;radius
: the circle’s radius.
We therefore propose the following language-neutral declaration of a Circle
type.
type Circle x1 float y1 float radius float
As for rectangles, we might use the following four fields.
x1
: the x-coordinate of one point (say, the center or upper left corner);y1
: the y-coordinate of that same point;width
: the rectangle’s width;height
: the rectangle’s height.
These fields imply the following declaration of a Rectangle
type.
type Rectangle x1 float y1 float width float height float
Objects are nothing new
Before continuing, we point out that we have already started working with objects when we needed to represent a virtual “canvas”, and the above representation is already how we conceptualize rectangles on such a canvas.
For example, in Go, we introduced graphics via a “canvas” object contained within a canvas.go
file that contained the following type declaration. Explanations of each field are shown via a comment at the end of the line.
type Canvas struct { gc *draw2d.ImageGraphicContext // a "pen" object img image.Image // an "image" object width int // the canvas width, in pixels height int // the canvas height, in pixels }
Furthermore, in Python, our canvas was a pygame.Surface
object, and we encountered the pygame.draw.rect()
when introducing graphics. This function takes the following parameters as input:
- a
pygame.Surface
object representing the canvas; - a
tuple
(i.e., array) of length 3 (r
,g
,b
) representing the color of the rectangle in RGB format, e.g.(255, 0, 0)
for red. - a
tuple
of length 4(x, y, width, height)
specifying the x- and y-coordinates of the rectangle’s top left corner, along with the rectangle’s width and height, in pixels.
Allowing for rectangles to rotate
The astute reader may have noticed that our rectangle suffers from a critical flaw: our representation of a rectangle can only represent upright rectangles!
STOP: How would you represent an arbitrary rectangle in 2-D space?
We present two approaches for how we could improve our Rectangle
type. First, we could add a rotation
field that would store the angle of rotation of the figure (e.g., between 0 and 2π radians). The adjusted Rectangle
type declaration is below.
type Rectangle x1 float y1 float width float height float rotation float
Second, we could instead specify the rectangle as containing four points: (x1
, y1
), (x2
, y2
), (x3
, y3
), and (x4
, y4
). The following representation contains these eight fields.
type Rectangle x1 float y1 float x2 float y2 float x3 float y3 float x4 float y4 float
This representation is a little unwieldy because we are abstracting the rectangle not as eight floating-point numbers, but as four points. Instead, a Rectangle
object will now have four OrderedPair
fields, and an OrderedPair
object in turn contains two floating-point numbers as its fields. Top-down programming applies to object design too!
type Rectangle p1 OrderedPair p2 OrderedPair p3 OrderedPair p4 OrderedPair type OrderedPair x float y float
STOP: We now have two representations of an arbitrary rectangle. Which do you prefer?
One might prefer the first abstraction of a rectangle, in which we introduced a rotation
field, since it requires only five total pieces of information in order to store the shape, compared to eight for the second abstraction.
This focus on economy is reasonable, but it misses a larger reason to reject the second representation, which is that many collections of four points do not form the corners of a rectangle! The figure below shows one such collection.

This example may be simple, but it exemplifies the need for care when abstracting even seemingly simple objects so that we don’t create problems later for the user.
More generally, the central premise of abstraction is to reduce an object to the critical attributes that define it, and which distinguish it from other types of objects. After all, whereas our brains envision an abstract concept of a rectangle, a computer does not (yet) possess that intelligence and sees only a type that is associated with a collection of fields.
Accessing the fields of an object
As we can declare variables that have type integer or string, once we have created an object type, we can declare objects having that type. We will implement this idea in this chapter’s code alongs.
We also can pass an object as input to a function or return it as output. For example, once we know the width and height of a rectangle, or the radius of a circle, we can immediately infer the shape’s area. More generally, because the fields of an object abstract that object, we can infer properties of the object beyond just its fields. The following two Area()
functions take as input, respectively, a Rectangle
object r
and a Circle
object c
; they show the generally accepted practice of accessing the field of a given object using a period, followed by the name of the field. The second function uses pi
to denote the mathematical constant π (3.14159…), which can typically be accessed from a language’s math
package.
Area(r) return r.width * r.height Area(c) return c.radius * c.radius * pi
Exercise: Write two pseudocode functionsPerimeter()
that take a Rectangle object and a Circle object as input, respectively, and return each of their perimeters.
Just as we can pass an object as input to a function, we can return the object as the output of a function. For example, say that we want to move, or translate, a shape from one position to another, as shown in the figure below.

We can think of translating a shape as moving it a
units in the x-direction and b
units in the y-direction, where a
and b can be positive or negative depending on whether we are moving right or left, or up or down. As a result, when writing our Translate()
functions, we simply need to take a
and b as inputs in addition to the shape; after updating the shape, we will return it. (We will see an even better way to translate shapes in this chapter’s code alongs.)
Translate(r, a, b) r.x1 = r.x1 + a r.y1 = r.y1 + b return r Translate(c, a, b) c.x1 = c.x1 + a c.y1 = c.y1 + b return c
Note: You may have noticed that these functions, apart from taking aCircle
vs. aRectangle
as input, are identical. This observation hints at a larger principle of object-oriented programming called polymorphism, in which we treat both theCircle
andRectangle
objects as instances of a more generalShape
object, which can be translated in the same way regardless of the type of shape it represents.
Exercise: Write two functions calledScale()
that scale the size of aRectangle
or aCircle
by an input factorf
. (As a result, the area of the shape will increase by the square off
.)
Looking ahead
In the next lesson, we will extend what we have learned about abstraction to plan objects for our gravity simulator. Before moving on, we give you the chance to think about this question on your own.
Exercise: What object types do we need to build a gravity simulator? What fields should each one have? What functions might be useful to have for these objects?