As you may have discovered, it is legal to make more than one assignment to the same variable. A new assignment makes an existing variable refer to a new value (and stop referring to the old value).
atom bruce = 5 ? bruce bruce = 7 ? bruce --5 --7
This is called multiple assignment . In the output of this program, the first time bruce is printed, his value is 5, and the second time, his value is 7 . Here is what multiple assignment looks like in a state diagram:
With multiple assignment it is especially important to distinguish between an assignment command and a statement of equality. Because Euphoria uses the equal sign ( = ) for assignment, it is tempting to interpret a command like a = b as a statement of equality. It is not!
First, equality is a symmetric relation and assignment is not. For example, in mathematics, if a = 7 then 7 = a . But in Euphoria, the command a = 7 is legal and 7 = a is not.
Furthermore, in mathematics, a statement of equality is either false or true--for all time. If a = b now, then a will always equal b. In Euphoria, an assignment command can make two variables equal, but they don't have to stay that way:
atom a, b a = 5 b = a -- "a and b are now equal" a = 3 -- "a and b are no longer equal"
The third line changes the value of a but does not change the value of b, so they are no longer equal.
Although multiple assignment is frequently helpful, you should use it with caution. If the values of variables change frequently, it can make the code difficult to read and debug.
One of the most common forms of multiple assignment is an update , where the new value of the variable depends on the old.
x = x+1
This means get the current value of x, add one, and then update x with the new value. If you try to update a variable that doesn't exist, you get an error, because Euphoria evaluates the right side before it assigns a value to x:
x = x+1produces:
x has not been declared x =x+1 ^
Before you can update a variable, you have to initialize it, usually with a simple assignment:
atom x x = 0 x = x+1
Updating a variable by adding 1 is called an increment; subtracting 1 is called a decrement. Because updating is so common, there is a shorthand for it:
x += 1 -- increment by one x -= 1 -- decrement by one
Computers are often used to automate repetitive tasks. Repeating identical or similar tasks without making errors is something that computers do well and people do poorly.
We have seen two programs, countdown and printN, that use recursion to perform repetition, which is also called iteration. Because iteration is so common, Euphoria provides several language features to make it easier. The first feature we are going to look at is the while command.
Here is what countdown() looks like with a while command:
procedure countdown( atom n ) while n > 0 then ? n n = n - 1 end while puts(1, "Blastoff!" ) end procedure
Since we removed the recursive call, this procedure is not recursive.
You can almost read the while command as if it were English. It means, "While n is greater than 0, continue displaying the value of n and then reducing the value of n by 1. When you get to 0, display the word Blastoff!"
More formally, here is the flow of execution for a while command:
This type of flow is called a loop because the third step loops back around to the top. Notice that if the condition is false the first time through the loop, the commands inside the loop are never executed.
The body of the loop should change the value of one or more variables so that eventually the condition becomes false and the loop terminates. Otherwise the loop will repeat forever, which is called an infinite loop. An endless source of amusement for computer scientists is the observation that the directions on shampoo, "Lather, rinse, repeat", are an infinite loop.
In the case of countdown(), we can prove that the loop terminates because we know that the value of n is finite, and we can see that the value of n gets smaller each time through the loop, so eventually we have to get to 0. In other cases, it is not so easy to tell:
procedure Sequence( n ) while n != 1 do ? n if remainder( n, 2 ) = 0 then -- n is even n = n/2 else -- n is odd n = n*3+1 end while end procedure
The condition for this loop is n != 1, so the loop will continue until n is 1, which will make the condition false.
Each time through the loop, the program outputs the value of n and then checks whether it is even or odd. If it is even, the value of n is divided by 2. If it is odd, the value is replaced by n*3+1. For example, if the starting value (the argument passed to sequence) is 3, the resulting sequence is 3, 10, 5, 16, 8, 4, 2, 1.
Since n sometimes increases and sometimes decreases, there is no obvious proof that n will ever reach 1, or that the program terminates. For some particular values of n, we can prove termination. For example, if the starting value is a power of two, then the value of n will be even each time through the loop until it reaches 1. The previous example ends with such a sequence, starting with 16.
The hard question is whether we can prove that this program terminates for all positive values of n. So far, no one has been able to prove it or disprove it!
Sometimes you don't know it's time to end a loop until you get half way through the body. In that case you can use the exit command to jump out of the loop.
For example, suppose you want to take input from the user until they type done. You could write:
object line while 1 do puts(1, ">" ) line = gets(0) if equal( line, "done" & 10 ) then exit end if end while puts(1, "Done...!" )
The loop condition is 1, which is always true, so the loop runs until it hits the exit command. Each time through, it prompts the user with an angle ( > ) bracket. If the user types done, the exit command exits the loop. Otherwise the program echoes whatever the user types and goes back to the top of the loop. Here's a sample run:
> not done not done > done Done...!
This way of writing while loops is common because you can check the condition anywhere in the loop (not just at the top) and you can express the stop condition affirmatively ("stop when this happens") rather than negatively ("keep going until that happens.").
Loops are often used in programs that compute numerical results by starting with an approximate answer and iteratively improving it.
For example, one way of computing square roots is Newton's method. Suppose that you want to know the square root of a. If you start with almost any estimate, x, you can compute a better estimate with the following formula:
y = ( x + a/x ) / 2
For example, if a is 4 and x is 3:
atom a = 4 atom x = 3 atom y = (x + a/x) / 2 ? y -- 2.16666666667
Which is closer to the correct answer (root of 4 is 2). If we repeat the process with the new estimate, it gets even closer:
x = y y = (x + a/x) / 2 ? y -- 2.00641025641
After a few more updates, the estimate is almost exact:
x = y y = (x + a/x) / 2 ? y --2.00001024003 x = y x = (x + a/x) / 2 ? y --2.00000000003
In general we don't know ahead of time how many steps it takes to get to the right answer, but we know when we get there because the estimate stops changing:
x = y y = (x + a/x) / 2 ? y x = y y = (x + a/x) / 2 ? y -- 2 -- 2
When y = x, we can stop. Here is a loop that starts with an initial estimate, x, and improves it until it stops changing:
atom a = 4 atom x = 3 atom y while 1 do ? x y = (x + a/x) / 2 if y = x then exit end if x = y end while
For most values of a this works fine, but in general it is dangerous to test decimal value equality. Decimal values (aka floating-point values) are only approximately right: most rational numbers, like 1/3, and irrational numbers, like root 2, can't be represented exactly with a decimal value in a computer.
Rather than checking whether x and y are exactly equal, it is safer to use the built-in function abs() to compute the absolute value, or magnitude, of the difference between them:
include math.e if abs(y-x) < epsilon then exit end if
Where epsilon has a value like 0.0000001 that determines how close is close enough.
toms addendum
atom a = 4 atom x = 3 while 1 do ? x atom y = (x + a/x) / 2 if y = x then exit end if x = y end while
?? is there a penalty for declaring y inside the while loop
?? is there any advantage in doing this
Newton's method is an example of an algorithm: it is a mechanical process for solving a category of problems (in this case, computing square roots).
It is not easy to define an algorithm. It might help to start with something that is not an algorithm. When you learned to multiply single-digit numbers, you probably memorized the multiplication table. In effect, you memorized 100 specific solutions. That kind of knowledge is not algorithmic.
But if you were "lazy," you probably cheated by learning a few tricks. For example, to find the product of n and 9, you can write n-1 as the first digit and 10-n as the second digit. This trick is a general solution for multiplying any single-digit number by 9. That's an algorithm!
Similarly, the techniques you learned for addition with carrying, subtraction with borrowing, and long division are all algorithms. One of the characteristics of algorithms is that they do not require any intelligence to carry out. They are mechanical processes in which each step follows from the last according to a simple set of rules.
In my opinion, it is embarrassing that humans spend so much time in school learning to execute algorithms that, quite literally, require no intelligence.
On the other hand, the process of designing algorithms is interesting, intellectually challenging, and a central part of what we call programming.
Some of the things that people do naturally, without difficulty or conscious thought, are the hardest to express algorithmically. Understanding natural language is a good example. We all do it, but so far no one has been able to explain how we do it, at least not in the form of an algorithm.
One of the things loops are good for is generating tabular data. Before computers were readily available, people had to calculate logarithms, sines and cosines, and other mathematical functions by hand. To make that easier, mathematics books contained long tables listing the values of these functions. Creating the tables was slow and boring, and they tended to be full of errors.
When computers appeared on the scene, one of the initial reactions was, "This is great! We can use the computers to generate the tables, so there will be no errors." That turned out to be true (mostly) but shortsighted. Soon thereafter, computers and calculators were so pervasive that the tables became obsolete.
Well, almost. For some operations, computers use tables of values to get an approximate answer and then perform computations to improve the approximation. In some cases, there have been errors in the underlying tables, most famously in the table the Intel Pentium used to perform floating-point division.
Although a log table is not as useful as it once was, it still makes a good example of iteration. The following program outputs a sequence of values in the left column and their logarithms in the right column:
atom x x = 1 while x < 10 do print(1, x ) puts(1, "\t" ) print(1, log( x ) puts(1, "\n" ) x = x + 1 end while
The string ( \t ) represents a tab character.
As characters and strings are displayed on the screen, an invisible marker called the cursor keeps track of where the next character will go. After a ? output command, the cursor normally goes to the beginning of the next line.Otherwise, the cursor is at the next position in the existing line.
The tab character shifts the cursor to the right until it reaches one of the tab stops. Tabs are useful for making columns of text line up, as in the output of the previous program:
1.0 0.0 2.0 0.69314718056 3.0 1.09861228867 4.0 1.38629436112 5.0 1.60943791243 6.0 1.79175946923 7.0 1.94591014906 8.0 2.07944154168 9.0 2.19722457734
If these values seem odd, remember that the log function uses base e. Since powers of two are so important in computer science, we often want to find logarithms with respect to base 2. To do that, we can use the following formula:
log2 = ( loge ) / ( loge 2 )
Changing the output commands to:
print(1, x ) puts(1, '\t' ) print(1, log( x ) / log( 2 ) ) puts(1, '\n' )
yields:
1.0 0.0 2.0 1.0 3.0 1.58496250072 4.0 2.0 5.0 2.32192809489 6.0 2.58496250072 7.0 2.80735492206 8.0 3.0 9.0 3.16992500144
We can see that 1, 2, 4, and 8 are powers of two because their logarithms base 2 are round numbers. If we wanted to find the logarithms of other powers of two, we could modify the program like this:
atom x x = 1 while x < 100 do print(1, x ) puts(1, '\n' ) print(1, log( x ) / log( 2 ) puts(1, '\n' ) x = x * 2 end while
Now instead of adding something to x each time through the loop, which yields an arithmetic sequence, we multiply x by something, yielding a geometric sequence. The result is:
1.0 0.0 2.0 1.0 4.0 2.0 8.0 3.0 16.0 4.0 32.0 5.0 64.0 6.0
Because of the tab characters between the columns, the position of the second column does not depend on the number of digits in the first column.
Logarithm tables may not be useful any more, but for computer scientists, knowing the powers of two is!
The printf() procedure lets you format your output in one step. The previous procedure would now look like:
atom x x = 1 while x < 100 do printf(1, "%d\t%d\n", { x, log( x ) / log( 2 ) } ) x = x * 2 end while
The ( " " ) delimited portion determines the formatting of your output. The %d is a placeholder for a decimal number value, the ( \t ) is a tab, and the ( \n ) is a newline. The ( { } ) sequence portion is a list of the values you want displayed.
When displaying a string the same conventions may be used to control the display. The backslash character in ( \t ) indicates the beginning of an escape sequence. Escape sequences are used to represent invisible characters like tabs and newlines. The sequence ( \n ) represents a newline. An escape sequence can appear anywhere in a string; in the example, the tab escape sequence is the only thing in the string.
How do you think you represent a backslash in a string?
A two-dimensional table is a table where you read the value at the intersection of a row and a column. A multiplication table is a good example. Let's say you want to print a multiplication table for the values from 1 to 6.
A good way to start is to write a loop that prints the multiples of 2, all on one line:
integer i i = 1 while i < = 6 do printf(1, "%d", 2*i ) i = i + 1 end while puts(1, '\n' )
The first line initializes a variable named i, which acts as a counter or loop variable. As the loop executes, the value of i increases from 1 to 6. When i is 7, the loop terminates. Each time through the loop, it displays the value of 2*i, followed by three spaces.
The output of the program is:
2 4 6 8 10 12
So far, so good. The next step is to encapsulate !#example). and generalize.
Encapsulation is the process of wrapping a piece of code in a function, allowing you to take advantage of all the things functions are good for. You have seen two examples of encapsulation: printParity() in Section 4.5; and isDivisible() in Section 5.4.
Generalization means taking something specific, such as printing the multiples of 2, and making it more general, such as printing the multiples of any integer. This function encapsulates the previous loop and generalizes it to print multiples of n:
procedure printMultiples( atom n ) integer i i = 1 while i < = 6 do printf(1, "%d \t", n*i ) i = i + 1 end while puts(1, '\n' ) end procedure
To encapsulate, all we had to do was add the first line, which declares the name of the function and the parameter list. To generalize, all we had to do was replace the value 2 with the parameter n.
If we call this function with the argument 2, we get the same output as before.
With the argument 3, the output is:
3 6 9 12 15 18
With the argument 4, the output is:
4 8 12 16 20 24
By now you can probably guess how to print a multiplication table--by calling printMultiples() repeatedly with different arguments. In fact, we can use another loop:
integer i = 1 while i <= 6 do printMultiples( i ) i = i + 1 end while
Notice how similar this loop is to the one inside printMultiples(). All we did was replace the print command with a function call.
The output of this program is a multiplication table:
1 2 3 4 5 6 2 4 6 8 10 12 3 6 9 12 15 18 4 8 12 16 20 24 5 10 15 20 25 30 6 12 18 24 30 36
To demonstrate encapsulation again, let's take the code from the end of Section 6.5 and wrap it up in a procedure:
procedure printMultTable( ) integer i while i < = 6 do printMultiples( i ) i = i + 1 end while end procedure
This process is a common development plan. We develop code by writing lines of code outside any function, or typing them in to the interpreter. When we get the code working, we extract it and wrap it up in a function.
This development plan is particularly useful if you don't know, when you start writing, how to divide the program into functions. This approach lets you design as you go along.
You might be wondering how we can use the same variable, i, in both printMultiples and printMultTable. Doesn't it cause problems when one of the functions changes the value of the variable?
The answer is no, because the i in printMultiples() and the i in printMultTable() are not the same variable.
Variables created inside a function definition are local; you can't access a local variable from outside its " home" function. That means you are free to have multiple variables with the same name as long as they are not in the same function.
The stack diagram for this program shows that the two variables named i are not the same variable. They can refer to different values, and changing one does not affect the other.
i 2 1 3 n 3 i 2 1 printMultTable printMultiples
The value of i in printMultTable goes from 1 to 6. In the diagram it happens to be 3. The next time through the loop it will be 4. Each time through the loop, printMultTable calls printMultiples with the current value of i as an argument. That value gets assigned to the parameter n.
Inside printMultiples(), the value of i goes from 1 to 6. In the diagram, it happens to be 2. Changing this variable has no effect on the value of i in printMultTable().
It is common and perfectly legal to have different local variables with the same name. In particular, names like i and j are used frequently as loop variables. If you avoid using them in one function just because you used them somewhere else, you will probably make the program harder to read.
As another example of generalization, imagine you wanted a program that would print a multiplication table of any size, not just the six-by-six table. You could add a parameter to printMultTable():
procedure printMultTable( integer hight ) integer i i = 1 while i < = hight do printMultiples( i ) end while end procedure
We replaced the value 6 with the parameter high. If we call printMultTable() with the argument 7, it displays:
1 2 3 4 5 6 2 4 6 8 10 12 3 6 9 12 15 18 4 8 12 16 20 24 5 10 15 20 25 30 6 12 18 24 30 36 7 14 21 28 35 42
This is fine, except that we probably want the table to be square--with the same number of rows and columns. To do that, we add another parameter to printMultiples() to specify how many columns the table should have.
Just to be annoying, we call this parameter high, demonstrating that different functions can have parameters with the same name (just like local variables).
Here's the whole program:
procedure printMultiples( integer n, integer high ) integer i i = 1 while i < = high do printf( 1, "%d \t", n*i ) i = i + 1 end while puts(1, "\n" ) end procedure procedure printMultTable( integer high ) integer i i = 1 while i < = high do printMultiples( i, high ) i = i + 1 end while end procedure
Notice that when we added a new parameter, we had to change the first line of the function (the function heading), and we also had to change the place where the function is called in printMultTable().
As expected, this program generates a square seven-by-seven table:
1 2 3 4 5 6 7 2 4 6 8 10 12 14 3 6 9 12 15 18 21 4 8 12 16 20 24 28 5 10 15 20 25 30 35 6 12 18 24 30 36 42 7 14 21 28 35 42 49
When you generalize a routine appropriately, you often get a program with capabilities you didn't plan. For example, you might notice that, because ab = ba, all the entries in the table appear twice. You could save ink by printing only half the table. To do that, you only have to change one line f printMultTable().
Change
printMultiples(i, high)
to
printMultiples(i, i)
and you get
1 2 4 3 6 9 4 8 12 16 5 10 15 20 25 6 12 18 24 30 36 7 14 21 28 35 42 49
A few times now, we have mentioned "all the things functions are good for." By now, you might be wondering what exactly those things are. Here are some of them:
Well-designed functions are often useful for many programs. Once you write and debug one, you can reuse it.
As you start writing bigger programs, you might find yourself spending more time debugging. More code means more chances to make an error and more place for bugs to hide.
One way to cut your debugging time is "debugging by bisection." For example, if there are 100 lines in your program and you check them one at a time, it would take 100 steps.
Instead, try to break the problem in half. Look at the middle of the program, or near it, for an intermediate value you can check. Add a output command (or something else that has a verifiable effect) and run the program.
If the mid-point check is incorrect, the problem must be in the first half of the program. If it is correct, the problem is in the second half.
Every time you perform a check like this, you halve the number of lines you have to search. After six steps (which is much less than 100), you would be down to one or two lines of code, at least in theory.
In practice it is not always clear what the "middle of the program" is and not always possible to check it. It doesn?t make sense to count lines and find the exact midpoint. Instead, think about places in the program where there might be errors and places where it is easy to put a check. Then choose a spot where you think the chances are about the same that the bug is before or after the check.