Computer Science Canada Manual draft, chapters 1 - 14 with Foreword |
Author: | wtd [ Thu Nov 25, 2004 12:53 am ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Post subject: | Manual draft, chapters 1 - 14 with Foreword | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Foreword The purpose of this document is to introduce programming and good practices when programming. To accomplish this I will be showing an awful lot of code. Do not be daunted by this. First it's largely because I'm demonstrating the concepts in two languages for various reason explained in the next chapter. Secondly, much of the code in this document is exactly the same. Why would I do this? Well, quite simply, I believe that it helps the learning process to introduce at most one or two new concepts at once. I start out very simply and slowly add more to a program. By doing so I start out each chapter with something that looks very familiar from he previous chapter. I do expect that people reading this document will gain experience and confidence as they go along. I strongly advise readers to try each bit of code as they go along. Expecting this, I may provide less in the way of explanation in later chapters than I do in earlier chapters. I will not provide much in the way of explanation of syntax throughout the document, except perhaps where it's unusual or inconsistent in some way. I expect readers will be able to see patterns in code, and providing samples in two languages should help. A reader should look at a sample in Turing, and a sample in Ada95 and see the similarities, as well as the differences. For instance, in:
And:
The reader should notice that:
And:
Serve the same purpose, and should be able to discern therefore that the rest of the second program is specific to the language. Chapter 1: Background Why do we have computers? Computers exist for a singular purpose: to serve human beings, making complex, time-consuming calculations easy and quick. They can't think for us, but they can free us from having to do the menial, repetitive work. This allows us to focus on solving more interesting problems. What is programming? Programming is the art and science of making computers work for humans. When we describe a programming language as "low-level" or "high-level", we're talking about how far the language is from the machine. The farther away the language is from the machine, the more we've allowed ourselves to think in human terms, rather than machine terms. Aside from very specific situations, our goal should be to write programs for other humans who have to read and understand the program, and let the computer do the menial work of decoding what we mean. Achieving that is the focus of the following content. What language will this document use? For the purposes of this document, I'll be using Turing 4.05. It's a reasonably high-level language. If not as good in that regard as other languages, it is at least well-known among the target audience. Unfortunately, it is not freely available. As a result, I'll be giving examples in Ada95 as well, a language which you can freely use, and which bears a considerable syntactic similarity to Turing. An installer for Windows can be downloaded here. If you're using Linux, you can easily obtain Gnat via apt-get or other package management systems. Chapter 2: Hello, world! What good is a computer without output? Generally, if we don't see some result from running a program, there's very little to write the program. As a result, programming languages typically contain some standardized way to display output. What kind of output will we be seeing? That's a good question. The output we'll be seeing is purely text-based. Why not pictures? Graphics are all well and good, but the focus of this document is on good programming practices. The ideas presented within it apply equally well to text-based and graphical output, and by excluding graphics, we reduce the complexity of the program. I don't want people worrying about how to make their fonts purple, when they should be worrying about whether they'll be able to read their own code in a week. So, let's say "Hello, world!" Turing:
Ada95:
Picking it apart The Turing sample is very simple. "put" is a command which instructs the computer to output whatever follows it and then skip to the next line. "Hello, world!" is a "string": a set of characters we can print. The Ada sample is less simple, but the reasons for that lack of simplicity will be clear later. Chapter 3: What's in a language? So we've talked about programming languages, but we've yet to dispel a common source of confusion. In human languages we clearly draw the line between the rules of the languages, and the vocabulary. There are certain ways we put sentences together, and then there are the words themselves. It isn't unheard of for new words to get added to the English language, but the basic rules remain the same. The same applies to programming languages. We have parts of a programming language that are just part of the language, and inseparable from it. At the same time, we also have parts that correspond to words, and we can even create our own "words". Let's revisit "Hello, world!" In our Turing program, "put" is part of the language. This is the primary difference between the Turing and Ada examples. In the Ada example, "with", "use", "procedure", "is", "begin", and "end" are all part of the language, as are the semicolons which end statements. The two lines doing the work in each program are:
Whereas the first is an integral part of the language, the second is just a "word". In this case it's a procedure which prints out a string to the screen. It's part of the "Ada.Text_IO" package we included via the "with" statement. The "use" statement means I don't have to write the following.
Chapter 4: What's a procedure? In short, a procedure is a chunk of programming language code grouped together for the purpose of accomplishing some single task. By doing this, we also gain the ability to give it a name that has more meaning to us. Hello, again We return to our simple "Hello, world!" program.
This is pretty concise, and not too hard to figure out, but it doesn't really tell someone reading the program what I was trying to do. So we create a simple procedure called "greetWorld" that wraps up this code. Notice we gave it a verb-style name. That's because verbs do things in human languages, and procedures do the same in programming languages.
And then printing "Hello, world!" is as simple as writing the name of the procedure.
The same in Ada
Chapter 5: Parameters - Procedures get useful Let's face it Procedures as we've seen them aren't terribly useful. The code we've seen them contain is short, simple and unchanging. We need to view them in a different light to see their true potential. The mystical black box We talk about "black boxes" and what we're really talking about is an interface. The beauty of an interface is that we know what goes in, what comes out, and any side-effects, but we have no idea how it does that, and what's more, we don't care when we're using the interface. This frees us, when creating the procedure, to have the inside behave in any way we want. What is a parameter? A parameter is an input into a "black box". It's how we shove information into a procedure. Let's reconsider our "Hello, world!" program thus far. As it stands, we only greet this "world" character. What if we want to greet anyone? Well, if we want to be able to greet anyone, then we need to provide their name to the procedure. This means we need an input. Let's call it "name", and generalize our procedure to just "greet".
The ": string" is just telling our procedure that this input is going to be a string. If we try to provide anything else, the program will not run. The "+" is being used to join two strings. It's also an integral par of the language. Now, we can greet... well, let's greet my old buddy Bob.
Read that No, really, read it out loud. Isn't it beautiful? There are no weird, arbitrary programming language words, but rather pure coderspeak. For something as simple as this, this is perfection. The same in Ada
The syntax is a bit different, but the net effect is the same.
This is still beautiful to behold. Chapter 6: Functions So, we now have the ability to greet any given Subject or entity. Still, there's something missing. Flexibility We can print "Hello, Bob!" or "Hello, world!", but what if we want to something else with that greeting? I mentioned earlier that "black boxes" can accept input, cause side-effects, and output information. We've seen two of those steps. The "greet" procedure both accepted input and had the side effect of printing a greeting. However, it didn't output anything. To accomplish that, at the cost of not being able to cause side-effects, we want a function instead of a procedure. We'll call our function "greeting", since it will generate the actual greeting, but do nothing in particular with it.
Now, to greet my old friend Bob, I can simply "put" the result of the function.
This reads just about as well as the previous incarnation, and it's more flexible as well. The same in Ada
Chapter 7: Basic Input It's still inflexible We can create greetings for anyone we want, but we have no way for the user to put information into the program to change what it outputs. To avoid creating incredibly boring programs, this is essential. One thing first If we're going to have the user input their name, we need a place to store that name. Such a place is called a "variable". A variable stores a piece of data which we can then output or manipulate. Before a variable can be used, though, it has to be declared into existence. The type of variable we'll be using is a string. We've seen strings before. Now we'll just be giving a name to a string. Declaring a string variable in Turing looks like:
Similarly, in Ada95:
The "1 .. 40" bit specifies the length of the string. And we're off! Now, we just need to get the user's name and store it in "name". Turing:
Ada:
The "Name_End" variable is an integer which the Get_Line procedures stores the length of the string read in. As a result of needng this extra variable, our declaration should look like:
Putting it all together
In summary We've seen how having the ability to get user input can greatly enhance even the simplest of programs. We've also seen how complex Ada appears to be when it comes to strings. A quick lesson on Ada strings In Ada a string is just a collection of characters. When we declare a string, we set aside a certain number of characters worth of space in the computer's memory. We also give each of those characters a number, starting from one number and counting up to the number of the last character. Since the first and last numbers can be any numbers we'd like, we can find them with:
And:
Getting part of a string (say, everything except the last character) is as simple as:
Since the Get_Line procedure records the number of the end of the input, we can use this information to get the input string, as in:
Chapter 8: What We've Seen Basic output We can print things on the screen. This is incredibly important, because it allows our program to have results. Procedures and functions We can give meaningful names to sets of actions or calculations. No longer do we have to figure out what a piece of code is doing nearly as frequently. The name of the procedure or function can give us that information. Parameters Parameters give us a way to send information to a procedure to change what it does. Consider a car analogy: the program is the car. The engine is a procedure. The gas pedal is a parameter. We can change the behavior of the engine based on our gas pedal input. Push it farther down and the engine revs higher. Release pressure on the gas pedal and the engine slows down. Variables Variables give us a place to store information. Variables can store any type of information, but they do have a specific type, be it a string of characters or an integer. We can change what's stored in the variable, and we can provide a variable to a function or procedure as a parameter. Basic input We can get string input from the user and store it in a variable. This means that the user can interact with the program. Where do we go from here? The next few chapters will cover some boring, but fundamental concepts required for later chapters to make sense. There will also be some neat stuff, and further evolution of our "Hello, world!" program. Chapter 9: A Discriminating Greeter Where are we? We've created an elegant means of generating a greeting, which we can output. We can get a user's name for use in that greeting. Yet, no matter what the user inputs, we always just say "hello". It's mean, but let's say we want to laugh at a user if they have a certain name. If... The key is to use a conditional to change the output of the "greeting" function depending on the name input. The basic syntax should be apparent.
I almost wish it were more complex, so I could offer a lengthier explanation.
More options for comparisons We've seen the = operator. It compares two variables to see if they're the same. We can also determine if two variables are not equal, and test to see if one is greater than the other and vice versa. Further operators include "greater than or equal to" and "less than or equal to". Turing:
Ada:
Chapter 10: Ada95 and Strings The strings we've been using so far have been "bounded". What does bounded mean? Essentially, it means that our strings can only be of a certain length. This is great for strings that we write directly into the program. Consider:
This works just fine. Where it starts to go sour The problem is when you don't know the length of the string actually being stored in that variable. You get ugly things like:
This comes from the fact that we don't want the entire variable, but rather just the part written to by the earlier Get_Line procedure. What's the solution? The Ada.Strings.Unbounded package contains the Unbounded_String type. This type behaves more like string variables in Turing. The package also provides everything needed to interact with Unbounded_String variables, including To_String and To_Unbounded_String functions for converting back and forth between the two types. Additionally, the package Ada.Strings.Unbounded.Text_IO provides functions and procedures for doing input and output with Unbounded_String variables. The only major change here is that Get_Line is a function, rather than a procedure, which returns the string it reads in. As a result, instead of writing:
We can simply write:
The := operator simply assigns the thing on the right hand side to the variable on the left. Chapter 11: People Have Two Names In many of the world's civilizations, people have both a family name, and a given name. If we want a polite greeter, we should ask for both names, and greet the user with their full name. There's not a lot new, we just prompt the user for their first name, and then for their last name. Of course, we'll need extra variables to store the extra names.
But now I need a "greeting" function which can handle two names. The answer is a function which takes two arguments.
The whole thing comes together into:
The Ada equivalent is:
Chapter 11: Let's Greet Two People This is a pretty simple change from the last set of examples. We just need twice as many variables. To keep track of which is which, a simple approach is to simply tack a number onto the end of each variable name.
The problem with this So, now we have lots of variables floating around. The problem is that we could mix the first name for the first Subject up with the last name for the second Subject, and the output would look really weird. It might also be relatively hard to spot when we were trying to fix the program. After all, the whole thing would technically work fine. It just wouldn't work quite the way we want. The solution The solution is to group the first and last names somehow, so we don't deal with them individually, but rather a part of a single thing. A name is, after all, a single thing that just happens o have two components. Such a grouping is called a "record". Think of, let's say, a medical record. It's a single thing, but it's composed of several pieces of information. They're all on the same paper, so you can get the whole thing by just grabbing the paper, rather than having to piece it all together by hand. Of course, we can still access each piece of information separately. The code
Summary The fewer variables you have, and the better they're organized, the harder it is to get confused. Oh sure, maybe it's confusing now, but everyone learning gets confused. A little confusion now is a small price to pay for clarity for the rest of your career. Chapter 12: Let's Greet a Bunch of People Recap So we've not only seen that we can create lots of variables, and copy and paste code and end up with a program that greets two people. Now, what if we want to greet three people? Ok, we'll just add "name3" to the mix. What about four people, five people, seventeen people? The problem should be apparent. Eventually we're going to end up with a huge number of variables floating around, and pages upon pages of copied and pasted code. This is not elegant, or easy to modify. We need something better We need a single variable which can hold several names. In programming terms we call this an array. An array gives us space for a certain number of variables of a given type which we can easily access. Let's keep it simple and say we want to have three names. The code to create the array would look like:
Greeting all of thse people Now, one solution would be to just write out the code for the greeting 3 times. This will do an excellent job of demonstrating how to access elements of an array.
But... This results in a lot of copied and pasted code, and we know that the more code we write, the more likely we are to make a mistake. We need a way to run that same code repeatedly, but use a different name each time. Loops This is where loops come in. At its most basic a loop just does the same thing over and over without end. Fortunately, since that's rarely desirable, we can have conditions under which we exit out of the loop. In this case we'll use a counter variable. We'll start out with it set to one. We'll do the greeting, then add one to the counter and restart the loop. If the counter is four, then we're done, since the last name is number three. We'll see the syntax both for a loop and the exit from the loop.
In Ada:
Much better Isn't that better than writing that same snippet of code several times? Still, this seems so useful it'd be really handy if we didn't have to explicitly create the extra variable, test it, and update it. You're in luck! There is such a convenience. It's called a "for" loop, and the following will demonstrate how to use it in this case.
In Ada:
Still not flexible enough By convention, we've started with the first element in our array being number one. In Turing and Ada it doesn't have to be. It could be anything. Both languages provide a means to determine the numbers of the first and last elements of an array. Rather than hard-coding in "1 .. 3" we could instead provide the first and last numbers of the array. Then if we decided to change the array to eighteen thousand names, we could do so wthout having to change the loop at all.
In Ada:
Ada provides a further convenience Besides having the "First" and "Last" attributes, an array also has a "Range" attribute that can be used.
Take some time to digest this Arrays and looping are one of the handiest things a programmer can learn. They're also a big departure from the "old" unsophisticated way of doing a lot of work. Take some time. Read this half a dozen times if you need to. One more thing It doesn't add much to this program, but we can cycle through numbers in a loop backwards.
In Ada:
Chapter 13: Back to the Discriminating Greeter When last we talked about this... Previously we had an "if" statement which checked to see if the user's name was "Clarence", "Sid", or something else. Well, now we have two names. Hi, my name is Clarence Clarence! Let's say we want to ridicule a user if either of their names is Clarence or Sid. We have to combine two tests into one, checking if their first name is Clarence, or their last name is Clarence. The only thing we'll have to change is the greeting function. See how handle it was to break that down into a separate function?
Repeating code unnecessarily is one of the seven deadly sins
Given that we use this same type of match twice, and might use it several more times, we should probably break it off into a function.
Now we can rewrite the whole thing as:
In Ada:
Summary So, in this chapter we've seen how to use "or" to combine two tests. We could use "and" as well to determine if two tests are both true. Ada tip Ada provides additional syntax which can make this faster. The "or else" and "and then" operators will avoid tesing the second condition if they figure it's unnecessary.
Chapter 14: Checking Input So we've got a pretty sophisicated "hello, world" program. Don't worry, we can go further. More procedures One thing we haven't factored out into a procedure yet is the process of getting the first and last names.
Now, we can integrate this into our entire program.
What if the user doesn't answer? It's entirely possible that the user would just hit return without typing their name. In that case we could get some really odd output. What's the fix? We have to check the user's input to make sur there's something there. If there isn't, we just ask the question again. Potentially we could do the same thing over and over and over again. Does that sound like anything we've covered? It should. It sounds exactly like a loop, and that's exactly the way to approach this.
Of course the getLastName procedure will look nearly identical. The whole thing
In Ada:
Ada tip Many programming languages allow specifically for this kind of loop and provide syntax for it. Turing does not, oddly, enough, but Ada does. This kind of loop is called a "while" loop. In other words, we do something while some condition is true.
What have we learned? We've learned that we can keep doing something over and over until we get an acceptable to prevent conditions that could generate weird output. This is mundane but incredibly important. |