Programming C, C++, Java, PHP, Ruby, Turing, VB
Computer Science Canada 
Programming C, C++, Java, PHP, Ruby, Turing, VB  

Username:   Password: 
 RegisterRegister   
 Manual draft, chapters 1 - 14 with Foreword
Index -> Programming, Turing -> Turing Tutorials
View previous topic Printable versionDownload TopicRate TopicSubscribe to this topicPrivate MessagesRefresh page View next topic
Author Message
wtd




PostPosted: 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:

code:
put "Hello, world!"


And:

code:
with Ada.Text_IO; use Ada.Text_IO;
procedure Hello_World is
begin
   Put_Line("Hello, world!");
end Hello_World;


The reader should notice that:

code:
put "Hello, world!"


And:

code:
Put_Line("Hello, world!");


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:
code:
put "Hello, world!"


Ada95:
code:
with Ada.Text_IO; use Ada.Text_IO;
procedure Hello_World is
begin
   Put_Line("Hello, world!");
end Hello_World;


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:

code:
put "Hello, world!"


code:
Put_Line("Hello, world!");


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.

code:
Ada.Text_IO.Put_Line("Hello, world!");


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.

code:
put "Hello, world!"


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.

code:
procedure greetWorld
   put "Hello, world!"
end greetWorld


And then printing "Hello, world!" is as simple as writing the name of the procedure.

code:
greetWorld


The same in Ada

code:
with Ada.Text_IO; use Ada.Text_IO;
procedure Hello_World is
   procedure Greet_World is
   begin
      Put_Line("Hello, world!");
   end Greet_World;
begin
   Greet_World;
end Hello_World;


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".

code:
procedure greet (name : string)
   put "Hello, " + name + "!"
end 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.

code:
greet ("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

code:
with Ada.Text_IO; use Ada.Text_IO;
procedure Hello_World is
   procedure Greet(Name : in String) is
   begin
      Put_Line("Hello, " & Name & "!");
   end Greet;
begin
   Greet("Bob");
end Hello_World;


The syntax is a bit different, but the net effect is the same.

code:
Greet("Bob");


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.

code:
function greeting (name : string) : string
   result "Hello, " + name + "!"
end greeting


Now, to greet my old friend Bob, I can simply "put" the result of the function.

code:
put greeting ("Bob")


This reads just about as well as the previous incarnation, and it's more flexible as well.

The same in Ada

code:
with Ada.Text_IO; use Ada.Text_IO;
procedure Hello_World is
   function Greeting(Name : in String) return String is
   begin
      return "Hello, " & Name & "!";
   end Greeting;
begin
   Put_Line(Greeting("Bob"));
end Hello_World;


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:

code:
var name : string


Similarly, in Ada95:

code:
Name : String(1 .. 40);


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:
code:
get name


Ada:
code:
Get_Line(Name, Name_End);


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:

code:
Name : String;
Name_End : Integer;


Putting it all together

code:
function greeting (name : string) : string
   result "Hello, " + name + "!"
end greeting

var name : string

put "Please input your name:"
get name
put greeting (name)


code:
with Ada.Text_IO; use Ada.Text_IO;
procedure Hello_World is
   function Greeting(Name : in String) return String is
   begin
      return "Hello, " & Name & "!";
   end Greeting;
   
   Name : String(1 .. 40);
   Name_End : Integer;
begin
   Put_Line("Please input your name:");
   Get_Line(Name, Name_End);
   Put_Line(Greeting(Name(Name'First .. Name_End)));
end Hello_World;


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:

code:
String_Variable'First


And:

code:
String_Variable'Last


Getting part of a string (say, everything except the last character) is as simple as:

code:
String_Variable(String_Variable'First .. String_Variable'Last - 1)


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:

code:
Name(Name'First .. Name_End)


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.

code:
function greeting (name : string) : string
   if name = "Clarence" then
      result "Ha... Clarence..."
   elsif name = "Sid" then
      result "Sid?  That's hilarous!"
   else
      result "Hello, " + name + "!"
   end if
end greeting

var name : string

put "Please input your name:"
get name
put greeting (name)


I almost wish it were more complex, so I could offer a lengthier explanation.

code:
with Ada.Text_IO; use Ada.Text_IO;
procedure Hello_World is
   function Greeting(Name : in String) return String is
   begin
      if Name = "Clarence" then
         return "Ha... Clarence...";
      elsif Name = "Sid" then
         return "Sid?  That's hilarous!";
      else
         return "Hello, " & Name & "!";
      end if;
   end Greeting;
   
   Name : String(1 .. 40);
   Name_End : Integer;
begin
   Put_Line("Please input your name:");
   Get_Line(Name, Name_End);
   Put_Line(Greeting(Name(Name'First .. Name_End)));
end Hello_World;


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:
code:
=, not=, >, >=, <, <=


Ada:
code:
=, /=, >, >=, <, <=


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:

code:
Put_Line("Hello, world!");


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:

code:
Name(Name'First .. Name_End)


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:

code:
Get_Line(First_Name, First_Name_End);


We can simply write:

code:
First_Name := Get_Line;


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.

code:
var firstName, lastName : string

put "You are?"
get firstName
put "I didn't quite catch your last name..."
get lastName


But now I need a "greeting" function which can handle two names. The answer is a function which takes two arguments.

code:
function greeting (firstName, lastName : string) : string
   result "Hello, " + firstName + " " + lastName + "!"
end greeting


The whole thing comes together into:

code:
function greeting (firstName, lastName : string) : string
   result "Hello, " + firstName + " " + lastName + "!"
end greeting

var firstName, lastName : string

put "You are?"
get firstName
put "I didn't quite catch your last name..."
get lastName

put greeting (firstName, lastName)


The Ada equivalent is:

code:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings.Unbounded.Text_IO; use Ada.Strings.Unbounded.Text_IO;
procedure Hello_World is
   function Greeting(First_Name, Last_Name : in Unbounded_String) return Unbounded_String is
   begin
      return "Hello, " & First_Name & " " & Last_Name & "!";
   end Greeting;

   First_Name, Last_Name : Unbounded_String;
begin
   Put_Line("You are?");
   First_Name := Get_Line;
   Put_Line("I didn't quite catch your last name...");
   Last_Name := Get_Line;
   
   Put_Line(Greeting(First_Name, Last_Name));
end Hello_World;


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.

code:
function greeting (firstName, lastName : string) : string
   result "Hello, " + firstName + " " + lastName + "!"
end greeting

var firstName1, lastName1, firstName2, lastName2 : string

put "You are?"
get firstName1
put "I didn't quite catch your last name..."
get lastName1

put greeting (firstName1, lastName1)

put "You are?"
get firstName2
put "I didn't quite catch your last name..."
get lastName2

put greeting (firstName2, lastName2)


code:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings.Unbounded.Text_IO; use Ada.Strings.Unbounded.Text_IO;
procedure Hello_World is
   function Greeting(First_Name, Last_Name : in Unbounded_String) return Unbounded_String is
   begin
      return "Hello, " & First_Name & " " & Last_Name & "!";
   end Greeting;

   First_Name1, Last_Name1, First_Name2, Last_Name2 : Unbounded_String;
begin
   Put_Line("You are?");
   First_Name1 := Get_Line;
   Put_Line("I didn't quite catch your last name...");
   Last_Name1 := Get_Line;
   
   Put_Line(Greeting(First_Name1, Last_Name1));
   
   Put_Line("You are?");
   First_Name2 := Get_Line;
   Put_Line("I didn't quite catch your last name...");
   Last_Name2 := Get_Line;
   
   Put_Line(Greeting(First_Name2, Last_Name2));
end Hello_World;


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

code:
type Name :
   record
      first, last : string
   end record
   
function greeting (subject : Name) : string
   result "Hello, " + subject.first + " " + subject.last + "!"
end greeting

var name1, name2 : Name

put "You are?"
get name1.first
put "I didn't quite catch your last name..."
get name1.last

put greeting (name1)

put "You are?"
get name2.first
put "I didn't quite catch your last name..."
get name2.last

put greeting (name2)


code:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings.Unbounded.Text_IO; use Ada.Strings.Unbounded.Text_IO;
procedure Hello_World is
   type Name is record
      First, Last : Unbounded_String;
   end record;

   function Greeting(Subject : Name) return Unbounded_String is
   begin
      return "Hello, " & Subject.First & " " & Subject.Last & "!";
   end Greeting;

   Name1, Name2 : Name;
begin
   Put_Line("You are?");
   Name1.First := Get_Line;
   Put_Line("I didn't quite catch your last name...");
   Name1.Last := Get_Line;
   
   Put_Line(Greeting(Name1));
   
   Put_Line("You are?");
   Name2.First := Get_Line;
   Put_Line("I didn't quite catch your last name...");
   Name2.Last := Get_Line;
     
   Put_Line(Greeting(Name2));
end Hello_World;


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:

code:
type Name :
   record
      first, last : string
   end record
   
function greeting (subject : Name) : string
   result "Hello, " + subject.first + " " + subject.last + "!"
end greeting

var names : array 1 .. 3 of Name


code:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings.Unbounded.Text_IO; use Ada.Strings.Unbounded.Text_IO;
procedure Hello_World is
   type Name is record
      First, Last : Unbounded_String;
   end record;

   function Greeting(Subject : Name) return Unbounded_String is
   begin
      return "Hello, " & Subject.First & " " & Subject.Last & "!";
   end Greeting;
   
   Names : array (1 .. 3) of Name;
begin
end Hello_World;


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.

code:
type Name :
   record
      first, last : string
   end record
   
function greeting (subject : Name) : string
   result "Hello, " + subject.first + " " + subject.last + "!"
end greeting

var names : array 1 .. 3 of Name

put "You are?"
get names (1).first
put"I didn't quite catch your last name..."
get names (1).last

put greeting (names (1))

put "You are?"
get names (2).first
put"I didn't quite catch your last name..."
get names (2).last

put greeting (names (2))

put "You are?"
get names (3).first
put"I didn't quite catch your last name..."
get names (3).last

put greeting (names (3))


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.

code:
type Name :
   record
      first, last : string
   end record
   
function greeting (subject : Name) : string
   result "Hello, " + subject.first + " " + subject.last + "!"
end greeting

var names : array 1 .. 3 of Name

var counter : int := 1

loop
   exit when counter = 4
   
   put "You are?"
   get names (counter).first
   put"I didn't quite catch your last name..."
   get names (counter).last
   
   put greeting (names (counter))
   
   counter := counter + 1
end loop


In Ada:

code:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings.Unbounded.Text_IO; use Ada.Strings.Unbounded.Text_IO;
procedure Hello_World is
   type Name is record
      First, Last : Unbounded_String;
   end record;

   function Greeting(Subject : Name) return Unbounded_String is
   begin
      return "Hello, " & Subject.First & " " & Subject.Last & "!";
   end Greeting;
   
   Names : array (1 .. 3) of Name;
   Counter : Integer := 1;
begin
   loop
      exit when Counter = 4;
     
      Put_Line("You are?");
      Names(Counter).First := Get_Line;
      Put_Line("I didn't quite catch your last name...");
      Names(Counter).Last := Get_Line;
           
      Put_Line(Greeting(Names(Counter)));
     
      Counter := Counter + 1;
   end loop;
end Hello_World;


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.

code:
type Name :
   record
      first, last : string
   end record
   
function greeting (subject : Name) : string
   result "Hello, " + subject.first + " " + subject.last + "!"
end greeting

var names : array 1 .. 3 of Name

for counter : 1 .. 3
   put "You are?"
   get names (counter).first
   put"I didn't quite catch your last name..."
   get names (counter).last
   
   put greeting (names (counter))
end for


In Ada:

code:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings.Unbounded.Text_IO; use Ada.Strings.Unbounded.Text_IO;
procedure Hello_World is
   type Name is record
      First, Last : Unbounded_String;
   end record;

   function Greeting(Subject : Name) return Unbounded_String is
   begin
      return "Hello, " & Subject.First & " " & Subject.Last & "!";
   end Greeting;
   
   Names : array (1 .. 3) of Name;
begin
   for Counter in 1 .. 3
   loop     
      Put_Line("You are?");
      Names(Counter).First := Get_Line;
      Put_Line("I didn't quite catch your last name...");
      Names(Counter).Last := Get_Line;
           
      Put_Line(Greeting(Names(Counter)));
   end loop;
end Hello_World;


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.

code:
type Name :
   record
      first, last : string
   end record
   
function greeting (subject : Name) : string
   result "Hello, " + subject.first + " " + subject.last + "!"
end greeting

var names : array 1 .. 3 of Name

for counter : lower (names) .. upper (names)
   put "You are?"
   get names (counter).first
   put"I didn't quite catch your last name..."
   get names (counter).last
   
   put greeting (names (counter))
end for


In Ada:

code:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings.Unbounded.Text_IO; use Ada.Strings.Unbounded.Text_IO;
procedure Hello_World is
   type Name is record
      First, Last : Unbounded_String;
   end record;

   function Greeting(Subject : Name) return Unbounded_String is
   begin
      return "Hello, " & Subject.First & " " & Subject.Last & "!";
   end Greeting;
   
   Names : array (1 .. 3) of Name;
begin
   for Counter in Names'First .. Names'Last
   loop     
      Put_Line("You are?");
      Names(Counter).First := Get_Line;
      Put_Line("I didn't quite catch your last name...");
      Names(Counter).Last := Get_Line;
           
      Put_Line(Greeting(Names(Counter)));
   end loop;
end Hello_World;


Ada provides a further convenience

Besides having the "First" and "Last" attributes, an array also has a "Range" attribute that can be used.

code:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings.Unbounded.Text_IO; use Ada.Strings.Unbounded.Text_IO;
procedure Hello_World is
   type Name is record
      First, Last : Unbounded_String;
   end record;

   function Greeting(Subject : Name) return Unbounded_String is
   begin
      return "Hello, " & Subject.First & " " & Subject.Last & "!";
   end Greeting;
   
   Names : array (1 .. 3) of Name;
begin
   for Counter in Names'Range
   loop     
      Put_Line("You are?");
      Names(Counter).First := Get_Line;
      Put_Line("I didn't quite catch your last name...");
      Names(Counter).Last := Get_Line;
           
      Put_Line(Greeting(Names(Counter)));
   end loop;
end Hello_World;


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.

code:
type Name :
   record
      first, last : string
   end record
   
function greeting (subject : Name) : string
   result "Hello, " + subject.first + " " + subject.last + "!"
end greeting

var names : array 1 .. 3 of Name

for decreasing counter : lower (names) .. upper (names)
   put "You are?"
   get names (counter).first
   put"I didn't quite catch your last name..."
   get names (counter).last
   
   put greeting (names (counter))
end for


In Ada:

code:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings.Unbounded.Text_IO; use Ada.Strings.Unbounded.Text_IO;
procedure Hello_World is
   type Name is record
      First, Last : Unbounded_String;
   end record;

   function Greeting(Subject : Name) return Unbounded_String is
   begin
      return "Hello, " & Subject.First & " " & Subject.Last & "!";
   end Greeting;
   
   Names : array (1 .. 3) of Name;
begin
   for Counter in reverse Names'Range
   loop     
      Put_Line("You are?");
      Names(Counter).First := Get_Line;
      Put_Line("I didn't quite catch your last name...");
      Names(Counter).Last := Get_Line;
           
      Put_Line(Greeting(Names(Counter)));
   end loop;
end Hello_World;


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?

code:
type Name :
   record
      first, last : string
   end record
   
function greeting (subject : Name) : string
   if subject.first = "Clarence" or subject.last = "Clarence" then
      result "Ha... Clarence..."
   elsif subject.first = "Sid" or subject.last = "Sid" then
      result "Sid? That's hilarious!"
   else
      result "Hello, " + subject.first + " " + subject.last + "!"
   end if
end greeting

var names : array 1 .. 3 of Name

for counter : lower (names) .. upper (names)
   put "You are?"
   get names (counter).first
   put"I didn't quite catch your last name..."
   get names (counter).last
   
   put greeting (names (counter))
end for


Repeating code unnecessarily is one of the seven deadly sins

code:
subject.first = "Clarence" or subject.last = "Clarence"


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.

code:
function eitherNameIs (Subject : Name, badName : string) : boolean
   result Subject.first = badName or Subject.last = badName
end eitherNameIs


Now we can rewrite the whole thing as:

code:
type Name :
   record
      first, last : string
   end record
   
function eitherNameIs (Subject : Name, badName : string) : boolean
   result Subject.first = badName or Subject.last = badName
end eitherNameIs

function greeting (subject : Name) : string
   if eitherNameIs(subject, "Clarence") then
      result "Ha... Clarence..."
   elsif eitherNameIs(subject, "Sid") then
      result "Sid? That's hilarious!"
   else
      result "Hello, " + subject.first + " " + subject.last + "!"
   end if
end greeting

var names : array 1 .. 3 of Name

for counter : lower (names) .. upper (names)
   put "You are?"
   get names (counter).first
   put"I didn't quite catch your last name..."
   get names (counter).last
   
   put greeting (names (counter))
end for


In Ada:

code:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings.Unbounded.Text_IO; use Ada.Strings.Unbounded.Text_IO;
procedure Hello_World is
   type Name is record
      First, Last : Unbounded_String;
   end record;
   
   function Either_Name_Is(Subject : in Name; Bad_Name : in String) return Boolean is
   begin
      return Subject.First = Bad_Name or Subject.Last = Bad_Name;
   end Either_Name_Is;

   function Greeting(Subject : Name) return Unbounded_String is
   begin
      if Either_Name_Is(Subject, "Clarence") then
         return "Ha... Clarence...";
      elsif Either_Name_Is(Subject, "Sid") then
         return "Sid?  That's hilarious!";
      else
         return "Hello, " & Subject.First & " " & Subject.Last & "!";
      end if;
   end Greeting;
   
   Names : array (1 .. 3) of Name;
begin
   for Counter in Names'Range
   loop     
      Put_Line("You are?");
      Names(Counter).First := Get_Line;
      Put_Line("I didn't quite catch your last name...");
      Names(Counter).Last := Get_Line;
           
      Put_Line(Greeting(Names(Counter)));
   end loop;
end Hello_World;


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.

code:
function Either_Name_Is(Subject : in Name; Bad_Name : in String) return Boolean is
begin
   return Subject.First = Bad_Name or else Subject.Last = Bad_Name;
end Either_Name_Is;


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.

code:
procedure getFirstName (var subject : Name)
   put "You are?"
   get subject.first
end getFirstName

procedure getLastName (var subject : Name)
   put "I didn't quite catch your last name..."
   get subject.last
end getLastName


Now, we can integrate this into our entire program.

code:
type Name :
   record
      first, last : string
   end record
   
procedure getFirstName (var subject : Name)
   put "You are?"
   get subject.first
end getFirstName

procedure getLastName (var subject : Name)
   put "I didn't quite catch your last name..."
   get subject.last
end getLastName
   
function eitherNameIs (Subject : Name, badName : string) : boolean
   result Subject.first = badName or Subject.last = badName
end eitherNameIs

function greeting (subject : Name) : string
   if eitherNameIs(subject, "Clarence") then
      result "Ha... Clarence..."
   elsif eitherNameIs(subject, "Sid") then
      result "Sid? That's hilarious!"
   else
      result "Hello, " + subject.first + " " + subject.last + "!"
   end if
end greeting

var names : array 1 .. 3 of Name

for counter : lower (names) .. upper (names)
   getFirstName (names (counter))
   getLastName (names (counter))
   
   put greeting (names (counter))
end for


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.

code:
procedure getFirstName (var subject : Name)
   loop
      put "You are?"
      get subject.first
     
      exit when subject.first not= ""
   end loop
end getFirstName


Of course the getLastName procedure will look nearly identical.

The whole thing

code:
type Name :
   record
      first, last : string
   end record
   
procedure getFirstName (var subject : Name)
   loop
      put "You are?"
      get subject.first
     
      exit when subject.first not= ""
   end loop
end getFirstName

procedure getLastName (var subject : Name)
   loop
      put "I didn't quite catch your last name..."
      get subject.last
     
      exit when subject.last not= ""
   end loop
end getLastName
   
function eitherNameIs (subject : Name, badName : string) : boolean
   result subject.first = badName or subject.last = badName
end eitherNameIs

function greeting (subject : Name) : string
   if eitherNameIs(subject, "Clarence") then
      result "Ha... Clarence..."
   elsif eitherNameIs(subject, "Sid") then
      result "Sid? That's hilarious!"
   else
      result "Hello, " + subject.first + " " + subject.last + "!"
   end if
end greeting

var names : array 1 .. 3 of Name

for counter : lower (names) .. upper (names)
   getFirstName (names (counter))
   getLastName (names (counter))
   
   put greeting (names (counter))
end for


In Ada:

code:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings.Unbounded.Text_IO; use Ada.Strings.Unbounded.Text_IO;
procedure Hello_World is
   type Name is record
      First, Last : Unbounded_String;
   end record;
   
   procedure Get_First_Name(Subject : in out Name) is
   begin
      loop
         Put_Line("You are?");
         Subject.First := Get_Line;
     
         exit when Subject.First /= "";
      end loop;
   end Get_First_Name;
   
   procedure Get_Last_Name(Subject : in out Name) is
   begin
      loop
         Put_Line("I didn't quite catch your last name...");
         Subject.Last := Get_Line;
     
         exit when Subject.Last /= "";
      end loop;
   end Get_First_Name;
   
   function Either_Name_Is(Subject : in Name; Bad_Name : in String) return Boolean is
   begin
      return Subject.First = Bad_Name or Subject.Last = Bad_Name;
   end Either_Name_Is;

   function Greeting(Subject : Name) return Unbounded_String is
   begin
      if Either_Name_Is(Subject, "Clarence") then
         return "Ha... Clarence...";
      elsif Either_Name_Is(Subject, "Sid") then
         return "Sid?  That's hilarious!";
      else
         return "Hello, " & Subject.First & " " & Subject.Last & "!";
      end if;
   end Greeting;
   
   Names : array (1 .. 3) of Name;
begin
   for Counter in Names'Range
   loop     
      Get_First_Name(Names(Counter));
      Get_Last_Name(Names(Counter));
           
      Put_Line(Greeting(Names(Counter)));
   end loop;
end Hello_World;


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.

code:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings.Unbounded.Text_IO; use Ada.Strings.Unbounded.Text_IO;
procedure Hello_World is
   type Name is record
      First, Last : Unbounded_String;
   end record;
   
   procedure Get_First_Name(Subject : in out Name) is
   begin
      while Subject.First = ""
      loop
         Put_Line("You are?");
         Subject.First := Get_Line;
      end loop;
   end Get_First_Name;
   
   procedure Get_Last_Name(Subject : in out Name) is
   begin
      while Subject.Last = ""
      loop
         Put_Line("I didn't quite catch your last name...");
         Subject.Last := Get_Line;
      end loop;
   end Get_First_Name;
   
   function Either_Name_Is(Subject : in Name; Bad_Name : in String) return Boolean is
   begin
      return Subject.First = Bad_Name or Subject.Last = Bad_Name;
   end Either_Name_Is;

   function Greeting(Subject : Name) return Unbounded_String is
   begin
      if Either_Name_Is(Subject, "Clarence") then
         return "Ha... Clarence...";
      elsif Either_Name_Is(Subject, "Sid") then
         return "Sid?  That's hilarious!";
      else
         return "Hello, " & Subject.First & " " & Subject.Last & "!";
      end if;
   end Greeting;
   
   Names : array (1 .. 3) of Name;
begin
   for Counter in Names'Range
   loop     
      Get_First_Name(Names(Counter));
      Get_Last_Name(Names(Counter));
           
      Put_Line(Greeting(Names(Counter)));
   end loop;
end Hello_World;


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.
Sponsor
Sponsor
Sponsor
sponsor
Display posts from previous:   
   Index -> Programming, Turing -> Turing Tutorials
View previous topic Tell A FriendPrintable versionDownload TopicRate TopicSubscribe to this topicPrivate MessagesRefresh page View next topic

Page 1 of 1  [ 1 Posts ]
Jump to:   


Style:  
Search: