This will be the third and final part of talking about data types as part of the programming cycle on Tech Tuesdays. Part 1 introduced the basic issue. Part 2 went under the hood and showed how explicit type declarations are one way to solve the problem. Today we will look at another approach and none of this will make much sense if you haven’t read the first two parts.
Last week I showed how an address table can be used to keep track of what the current value of a variable is. I then also showed how these values might be represented in memory. At the time I did not point out that in the early days of computing memory was incredibly expensive. That in turn explains why back then it made sense to use as little memory for each value as possible. So if you knew that a variable would only take on values between 0 and 255, you should explicitly declare that variable as a byte (or a char) in your program. For instance, the Apple II on which I learned most of my early programming had 48 KB of main memory.
Fast forward to today where memory is hardly ever a constraint (for instance, I am writing this on MacBook with 4 GB of memory!). Now we no longer need to worry much if at all about memory as a constraint. So we can afford to store a lot of meta data (i.e. data about data) along with the actual data. For instance, we can include information about whether something is text or a number. For text we can include how long the text is and what kind of encoding it uses. For numbers we can include their precision or even their constituent parts (e.g. for rational numbers keep the numerator and denominator).
So let’s revisit the canonical problem from Part 1 which gave rise to our whole discussion of data types:
var a = 5; var b = "some short words"; var wtf = a + b;
We have now encountered two fundamentally different ways of resolving the problem. One was to include data type information in the program itself and use it at “compile time” (when translating into machine code) and the other is to keep the data types with the data and have it available at “run time” (when the program executes). As it turns out those two methods are not mutually exclusive. In fact we can think of a 2 x 2 matrix of possibilities as follows (flattened out into a table) that distinguish different programming languages from each other
Types in Code Types with Data Programming Languages
Yes Yes Java, C#
Yes No C, Pascal
No No — not possible —
Only the fourth combination is not possible at all because there is no mechanism to resolve the problem we identified. Now some people would say that Assembly language should go there, but that’s really not quite the right comparison because Assembly has no concept of the kind of higher level operations that would result in data type issues of the kind we have discussed throughout these posts.
Now you may have heard before about “strongly typed” versus “weakly typed” languages. Strongly typed languages enforce a lot of rules about data types whereas weakly typed ones do not. We can now begin to understand that the reality is more complicated because some languages have data type information only at compile time, some only at runtime and some at both. The degree of “type” enforcement and/or automatic conversion that is possible depends on what is available. For instance C can only check at compile time, so for any data type problems that cannot be detected then (and there are many), C programs may simply not work as expected.
Conversely, just because a language has run time information about data types available does *not* mean that it has to do automatic conversion. It could simply decide not to carry out the operation. PHP does a lot of automatic type conversion, Python does very little. We will revisit the topic of implicit conversion once more after we have learned about objects and object oriented programming, but to give you a preview you may want to read briefly about the wonderfully named “duck typing.”
That wound up being a lot more about data types than I originally thought I would write, but I felt that most introductions to data types that I have seen out there simply enumerate what’s available and state the rules for a particular language but provide no background as to why data types even exist and how to think about them at a higher level. Next week I think I will tackle reserved words and control structures.
Last Tech Tuesday we got started on data types as port of the ongoing cycle on programming. This post will not make sense without reading Part 1, so if you haven’t you should go back and do that now. We encountered the problem of “adding” two variables where one holds a number and the other holds some text. Today we will dig a bit deeper into this problem.
Let’s start by looking at the question what it even means for a variable to hold a value. As I explained in the post on literals, constants and variables, the variable is a name that we can use to refer to its value and the value can change as our program executes. At the heart of any way of doing this will eventually be an address in the computer’s memory. It’s at that address that we find a binary representation of the data in question. For instance, for text we might find a sequence of bytes where each byte represents a character encoded using ASCII with eventually a byte containing 0 (null) to mark the end of the text (that in any case is how it worked until we needed multi-byte characters for internationalization). For a whole number (an integer) we might simply find the binary version of that number.
Separately, we might have a kind of internal “phone book” or address table where for each variable name we keep track of the address in memory at which the value for that variable lives. So coming back to our example from Part 1 where we had:
var a = 5; var b = "some short words";
The “phone book” here would have two entries. One for the name “a” and one for the name “b” roughly as follows
a : 1000 b : 1002
Where 1000 and 1002 are addresses in the computer’s memory. When we look at the actual memory locations, we might see something like the following
1000 : 00 <- first byte of number 5
1001 : 05 <- second byte of number 5
1002 : 115 <- ASCII for "s"
1003 : 111 <- ASCII for "o"
1004 : 109 <- ASCII for "m"
1005 : 101 <- ASCII for "e"
1006 : 32 <- ASCII for " " (a blank space)
1007 ... 1015 : more ASCII codes
1016 : 100 <- ASCII for "d"
1017 : 115 <- ASCII for "s"
1018 : 0 <- NULL
So in this setup neither the address table nor the actual memory locations contain any information about what kind of data is held there. How could we then possibly have a computer program that knows what’s there and how to properly deal with it?
int a = 5; char b = "some short words";
The “int” in front of the “a” says that the name “a” will refer to an integer value. And the “char” in front of “b” says that “b” will refer to text made up of characters with the “” following the “b” saying there is more than one single character in “b”. When the computer takes our C program and turns it into machine code executable by the CPU, it can now make sure to use instructions that work for integers when dealing with variable “a” and instructions that work for sequences of characters when dealing with “b”.
var wtf = a + b;
In C, we would have to decide up front in our code whether we want “wtf” to be a number (int) or text (char). Let’s say we wrote
int a = 5; char b = "some short words"; char wtf = a + b;
leaving aside for the moment that this is *not* correct C we can see easily now how the computer has all the relevant information in the code of our program to do one of two things:
1. It could refuse to turn this program into machine code and instead complain that it does not know to combine an integer with a sequence of characters.
2. It could infer from our declaration that we want “wtf” to be a sequence of characters that it should turn anything that follows into a sequence of characters.
As it turns out, a so called C compiler, the program that turns a program written in C into machine code would choose option 1. In fact, in order to get this to work in C we need to use an explicit expression to both convert the number and combine it with the text.
int a = 5; char b = "some short words"; char wtf; sprintf(wtf, "%d%s", a, b);
The sprintf stands for something like “string print formatted”. We give it the variable where we want the combined text to go (“wtf”), then a a format - the “%d%s” which means a number followed by some text - and finally the variables holding the number and the text. Also note that we had to pick an explicit length for “wtf” to make this work.
Since it’s been a couple of weeks first off a quick reminder that we are in the middle of a Tech Tuesday cycle on programming. We left off learning about literals, constants and variables. If you don’t remember, now is probably a good time to briefly look back at it. As you do, you will notice that the examples involve both numbers, e.g. 42, and text, e.g. “Bill Gates.” In the last post these appear as literals and as values of named constants or variables.
As humans we bring a lot of knowledge with us that makes us recognize numbers and text as being different “types” of data. For instance, we know that we can do calculations with numbers. We immediately recognize an expression such as 35 + 7 and can calculate the result as 42. Similarly, we know that text has certain characteristics, such as consisting of a known number of characters (true even for a language such as Mandarin). For example “Bill Gates” consists of two separate words and a total of 10 characters if you count the blank space between the two words.
Now if you have been following Tech Tuesday from the beginning you will have learned that inside a computer both numbers and text are represented using the binary system. If not, you should go and quickly read that. You will see how numbers, texts, images, etc. are all represented by sequences of bits (0s and 1s). So here then comes the big question: how does the computer know what’s a number and what is text and what it can do with each?
var a = 5; var b = "some short words"; var wtf = a + b; alert(wtf);
As humans reading this code we see there is a potential problem here. We are creating two variables one containing a number (a) and the other containing text (b) and then are trying to create a third variable by “adding” the values of the first two. But inside the computer each of these is just a sequence of bit. So what should this code do? What will this code do? Note that these are two different questions. The first is normative (“should”) and the second is descriptive (“will”). Also note that this code has correct syntax, and what we are really investigating are the semantics (what does it mean?).
Now let’s take a look at the normative question instead. What “should” have happened? That turns out to be at the root of some very fierce arguments between people over the years. Before we get there, let’s try to make a list of possible outcomes:
1. The computer could throw some kind of “I don’t know what to do here” error when executing the program as it gets to the part where we try “var wtf = a + b;”.
2. The computer could present this error after “reading” the program but before “executing” it (that assumes the computer has an opportunity to analyze the code before executing it).
Data types in programming are all about which of these three possibilities is chosen in practice. Different programming languages and in some cases even different ways of using the same programming language will result in different outcomes being realized.
I feel this post is getting long, so you will have to wait until part 2 (hopefully next Tuesday) to learn more about how and why this issue gets resolved differently! In the meantime, I hope this provides a very different angle on data types than the usual clinical enumeration of booleans, integers, strings, etc.