Like training an apprentice

Instructing a computer automatically

Jack Holland
Understanding computer science
6 min readDec 5, 2013

--

Our hero from last time has grown quite a bit since we last saw them. Retired from the hero business, they have taken up a position at the eminent Hero Academy training neophytes to wield swords and treasure maps. While the teaching position is certainly comfortable and well-paid, the job is challenging. How does one transform a novice into an expert?

It would be one matter if the teachers could accompany their students throughout their lives, instructing them at each point. But this would be horribly impractical. Students need to learn to act on their own. Accordingly, professors need to teach students to act on their own. Eventually we’ll cover how a hero-professor could transmit their knowledge and experience to young heroes-in-training without having to follow the students around perpetually, but for now let’s deal with a much simpler task: how does one teach a computer to respond to a program?

In other words, if you were to design a computer, how would you enable programmers to write code in it? What kinds of features are necessary? Let’s assume you are designing a general purpose personal computer. It should be able to host all kinds of programs, from calculators to spreadsheets to video games to web browsers. To make things a bit more manageable, ignore the hardware issues and focus on one task in particular: if you were to design a language, what vocabulary would you give it? Imagine this language must be able to produce all of the programs mentioned above. Don’t worry about how you would implement the vocabulary — just contemplate what vocabulary would enable someone to write any program they wanted in your language. Think for a moment about how you would approach this.

One solution would be to lay out every possible program anyone would ever need to write:

  • calculator: creates a calculator
  • spreadsheet: creates a spreadsheet
  • video game: creates a video game
  • web browser: creates a web browser

This, of course, is wildly impractical. How could you even imagine all of the possibilities? You could spend a thousand years trying to enumerate all the possible programs and you would never finish the task. So there must be a better way. Perhaps the programs could be broken down into smaller components; if many programs shared the same core components, then the language would have to supply only those common components, not every possible program made out of them. A close analogy might be how a supermarket decides what to stock: if the supermarket stocked every possible meal anyone wanted to make, the store would be the size of a city. But supermarkets don’t do that. Instead, they stock a selection of ingredients that customers can make meals out of. Rice is used in lots of different meals, as are most spices and herbs. All the customer needs to know is what ingredients their desired meal is made out of. Then they can buy those ingredients and assemble the meal themselves.

Supermarkets are big enough as is — could you imagine if they stocked whole meals rather than ingredients?

This idea of supermarkets stocking a collection of ingredients from which customers can assemble meals is quite similar to how programming languages work, at least for our purposes. A language (supermarket) provides a vocabulary (list of ingredients) from which the programmers (customers) can assemble programs (meals). The question becomes what to put in this vocabulary. A critical fact, though, is that computers are based on numbers. Every kind of communication with computers eventually boils down to sending a stream of zeros and ones and every computation a computer makes must, at some point, reduce to arithmetic. However, this doesn’t mean we have to limit our thoughts to zeros and ones or arithmetic. Just because video games ultimately reduce to arithmetic doesn’t mean that video game designers have to think in such reductionist terms when they’re thinking about the big picture. In fact, it would be extremely difficult to program anything meaningful if programmers had to constantly write instructions at the level of basic math operations. The key is to build up levels of abstraction and let the lower levels handle the nitty-gritty math. But I’m getting ahead of myself…

Even games as complex and immersive as Skyrim are, at their core, just numbers (with hardware that turns certain numbers into colors on a screen and others into sounds)

Basic operations like those in arithmetic are the foundation of virtually every language because it’s the level at which computer programs have to start. The most complicated brain simulations, three-dimensional worlds, and colorful web pages all boil down to zeros, ones, and arithmetic, even if the designers of such programs don’t have to deal directly with this low-level math. To be clear, not all programming languages deal with numbers directly, but those that don’t are eventually translated into languages that do. Therefore, since most languages deal with numbers directly, and all languages do ultimately, we’re going to start with numbers.

Numbers!?

Numbers!? I know, most people don’t like them. Some people even hate them. Needless to say, whatever opinion you have of them is perfectly OK. I’m confident that with a gentle and clear enough introduction, even the most fanatic numerophobe can learn to appreciate the usefulness of numbers; understanding them is profoundly necessary to understanding computer science. By learning the math that computers operate on, we can develop an intuition for the abilities and limits of computers and how to make the most out of a programming language.

Let’s get back to this language we’re designing. And while we’re on the subject, let’s give it a name. The language should be so easy that writing programs in it are a piece of cake. So how about naming it Cake? To start, Cake’s only requirement is to do simple math. This limitation makes the project a lot easier. Here is a first stab at the vocabulary:

  • x + y: adds x and y
  • x - y: subtracts y from x
  • x * y: multiplies x and y
  • x / y: divides x by y

This should be fairly intuitive since it works just like regular arithmetic. 3 + 4 returns 7 and 10 * 8 returns 80. “returns” basically means “produces”. When an instruction is entered into Cake, Cake returns an answer. What if you want to do something more complex, like add 2 and 5, and multiply the result by 3? We’ll need to add another instruction to the vocabulary:

  • (x): considers x as a single unit

Let’s make this a bit clearer with the example mentioned above: (2 + 5) * 3 returns 21, since 2 plus 5 is 7 and 7 times 3 is 21. Thus, the parentheses tell Cake that everything inside of them should be dealt with before considering anything outside of them. Like the previous instructions, the parentheses work exactly like they do in regular math. And just like in regular math, Cake allows the parentheses to be nested as many times as needed, like this: ((6 - 2) * 5) / 2. This returns 10, since 6 minus 2 is 4, 4 times 5 is 20, and 20 divided by 2 is 10. The reason Cake allows nested parentheses without the vocabulary explicitly mentioning them is that the x in the (x) instruction above can be anything — even complicated instructions already enclosed within parentheses.

Because of this flexibility, arbitrarily complicated math instructions can be expressed with just these five instructions. Of course, trying to write very complicated math with just these instructions could get cumbersome very fast due to all the parentheses. We’ll need a way to conveniently and concisely reuse sequences of instructions over and over again, a topic we’ll explore next time.

--

--