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.


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:
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:
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:
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:
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:
double opacity;
double opacity;
We store a value in the cell with a subsequent assignment statement:
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:
double opacity = 0.25;
double opacity = 0.25;
In Ruby, there are no declarations. We introduce new names with a regular assignment:
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:
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.