Names and Values

Dear Computer

Chapter 1: Naming Things

Names and Values

The essence of a computer is just a few components: a processor, a bank of memory, and a bus transporting data between the processor and memory. As the program executes, the processor reads values from memory, produces new values using arithmetic and logic, and writes the values back to memory. This model of a computer is called the von Neumann architecture.

diagram showing how CPU connects to memory via a busdiagram showing how CPU connects to memory via a bus
A computer distilled down to its von Neumann architecture.

There are two related but distinct concepts in programs that execute on a von Neumann computer: names and values. We tend to lump both concepts together under the term variable, but distinguishing them can lead to a clearer understanding of how a program behaves.

In the most barren of programming environments, we have values but no names. Values are stored in memory at particular addresses. Computing without names is sometimes reasonable. You made it through most of your math classes by just entering values into a calculator. In a spreadsheet, we enter values in a grid and refer to them by their rows and columns. Languages like C and C++ still expose memory as an addressable bank of cells because working directly on memory is fast.

Goodbye Foo

Addresses don't communicate anything about a value beyond its location, which makes misinterpreting a value easy. Therefore, even the very earliest programming languages enabled programmers to attach meaningful names to cells.

Naming memory cells provides two clear benefits. First, a good name makes code easier to understand and maintain. Consider this Ruby program that assigns meaningless names to its data:

Ruby
b = a * 0.5
d = c / 2 * b * b
puts d
b = a * 0.5
d = c / 2 * b * b
puts d

Likely you will not understand the intent behind this code. Pity the programmer who has to fix a bug in this code months after it is written. The code would be even more obfuscated if it used addresses. Contrast it with this code that uses meaningful names:

Ruby
radius = diameter * 0.5
semicircle_area = Math::PI / 2 * radius * radius
puts semicircle_area
radius = diameter * 0.5
semicircle_area = Math::PI / 2 * radius * radius
puts semicircle_area

Second, names facilitate abstraction, which is movement away from the specific toward the general. For example, this code to calculate the cost of a taxed item is very specific:

Ruby
cost = 17.99 * (1 + 0.075)
cost = 17.99 * (1 + 0.075)

By naming the data with variables, the cost calculation may be expressed more abstractly:

Ruby
price = 17.99
tax_rate = 0.075
cost = price * (1 + tax_rate)
price = 17.99
tax_rate = 0.075
cost = price * (1 + tax_rate)

To produce a different cost, we change only the variable assignments and not the abstract formula. Abstract code changes less often than concrete code full of magic numbers. Also, a chunk of code whose data has been abstracted into variables is prepped for becoming a reusable function. The variables whose values will be changed become the function's parameters.

Making a Name for Yourself

In C and Java, we both reserve a memory cell and attach a name to it with a declaration. In these languages, a named cell is associated with a particular type of data. This Java declaration reserves 8 bytes of memory to hold a floating-point number and gives it the name opacity:

Java
double opacity;
double opacity;

We store a value in the cell with a subsequent assignment statement:

Java
opacity = 0.25;
opacity = 0.25;

The first time a value is assigned to a name is an initialization. In languages like Java, which have explicit declarations, the declaration and initialization are often combined into a single statement:

Java
double opacity = 0.25;
double opacity = 0.25;

In Ruby, there are no declarations. We introduce new names with a regular assignment:

Ruby
opacity = 0.25
opacity = 0.25

We can't tell by looking at this one line of code if it is an initialization or a reassignment of a name introduced elsewhere. Additionally, because there's no type associated with names in Ruby, opacity may be reassigned later to a value of a different type:

Ruby
opacity = 0.25
opacity = "invisible"
opacity = 0.25
opacity = "invisible"

Ruby's flexibility and economy of expression mean we can write code very quickly, but we can also write bugs very quickly. As we see here, we might accidentally change to a value of a unexpected type. There is no compiler to catch this; such bugs will only be detected when the offending code runs. Thorough testing is paramount when writing Ruby programs.

← Ruby as a LensCombinatorics of Names →