Eu) no longer needs routin_id() to reference a "forward" declared routine
In the context of programming, a routine is a named sequence of commands that performs a computation; and may be either a procedure or a function. When you define a routine, you specify the name and the sequence of commands. Later, you can call the routine by name.
We have already seen one example of a routine call . This example is for a function, so it is a function call:
? atom(32) -- 1 ? integer(32) -- 1The name of the function is atom(). The expression in parentheses is called the argument of the function. The returned value for this function is either 0 or 1, meaning false or true.
It is common to say that a function takes an argument and returns a result. The result is called the return value . Euphoria has two kinds of routines. A routine may be either a function or a procedure. Therefore "routine" is a collective term I will use whenever a description applies to both functions and procedures. (You will never actually create a routine; only a function or a procedure.)
Euphoria provides functions that convert values from one type to another. The floor() function takes any value and converts it to an integer, if it can, or complains otherwise:
? floor( 32 ) -- 32floor() can convert decimal-point values to integers, but it doesn't round off; it chops off the fraction part:
? floor( 3.99999 ) -- 3 ? floor( -2.3 ) -- -2There is no need to convert a numerical value into an atom--all values are acceptible.
Finally, sprint() converts its argument to a string:
include std.e sequence str = sprint( 32 ) -- "32" when converted to string str = sprint( 3.14159 ) -- "3.14159" when converted to string ? str -- { 51,46,49,52,49,53,57}The result is something that "looks-like" a number, but is actually a string.
You may also do the reverse using value():
include std.e atom x = value( "3.14159" ) ? x -- {0, 3.14159} ? x[2] -- 3.14159The first element returned by value() is an error code; in this case 0, meaning zero problems.
We have converted something that "looks-like" a number, into an actual number.
The most common mathematical functions are built-in the Euphoria interpreter.
Many, less common, math functions are found in the include file math.e that provides a wide choice mathematical functions. An include is a file that contains a collection of related routines and constants.
Before we can use the file, we have to "include" it:
include std/math.e
The include command makes the contents of the file available inside your program.
include std/math.e ratio = signal_power / noise_power decibels = 10 * log10( ratio )
radians = 0.7 height = sin( radians )The math.e include provides log10(), the logarithm base 10, for the signal-to-noise ratio calculation.
A function called log() computes logarithms using base e. It is a more "fundamental" function so it is built-in the Euphoria interpreter itself-- so no include file is needed.
The second example finds the sine of radians. The name of the variable is a hint that sin and the other trigonometric functions (cos, tan, etc.) take arguments in radians. To convert from degrees to radians, divide by 360 and multiply by TWOPI:
include std/math.e atom degrees = 45 atom radians = degrees / 360.0 * TWOPI ? sin( radians ) -- 0.707106781187The include file makes TWOPI available for calculations. The value of this variable is an approximation of 6.283..., accurate to about 15 digits. If you know your trigonometry, you can check the previous result by comparing it to the square root of two divided by two:
sqrt( 2 ) / 2 -- 0.707106781187Since converting between radians and degrees is common, two functions are provided to do this:
include std/math.e ? deg2rad( 60 ) -- 0.0873 ? rad2deg( 0.0873 ) -- 60If you look at the include/std directory of the Euphoria files you will find many useful inlcude files that extend the capability of Euphoria. Use the documentation to discover what is available. If your program needs just a specific file, just include that one file: include std/math.e . If your program will need routines from several include files it may be more convenient to include them all:
include std.eThe "extra" includes do not become part of your executing program; only the required files are actually used.
So far, we have looked at the elements of a program--variables, expressions, and commands--in isolation, without talking about how to combine them.
One of the most useful features of programming languages is their ability to take small building blocks and compose them. For example, the argument of a function can be any kind of expression, including arithmetic operators:
x = sin(degrees / 360 * TWOPI )And even function calls:
x = power( log(x+1), E )Almost anywhere you can put a value, you can put an arbitrary expression, with one exception: the left side of an assignment command has to be a variable name. Any other expression on the left side is a syntax error.
minutes = hours * 60 -- right hours * 60 = minutes -- wrong! -- Syntax error - expected to see =, +=, -=, *=, /= or &= -- hours * 60 = minutes -- ^
So far, we have only been using the functions that come with Euphoria, but it is also possible to add new routines. A routine definition specifies the name of a new routine and the sequence of commands that execute when the routine is called.
Here is an example for a procedure:
procedure print_lyrics() puts(1, "I'm a lumberjack, and I'm okay." ) puts(1, "I sleep all night and I work all day." ) end procedure
procedure is a keyword that indicates that this is a procedure definition. The name of the procedure is print_lyrics(). The rules for routine names are the same as for variable names: letters, numbers and some punctuation marks are lega, but the first character can't be a number. You can't use a keyword as the name of a routine, and you can't have a variable and a routine with the same name.
The empty parentheses after the name indicate that this procedure doesn't take any arguments.
The first line of the routine definition is called the header By convention, the the code block is often indented by four spaces. The code block can contain any number of commands. A code block may not contain the definition for another routine.
The strings in the puts() commands are enclosed in double quotes.
To end the function, you have to enter an empty line (this is not necessary in a script).
The syntax for calling the new routines is the same as for built-in routines:
print_lyrics() -- I'm a lumberjack, and I'm okay. -- I sleep all night and I work all day.Once you have defined a routine, you can use it inside another routine. For example, to repeat the previous refrain, we could write a procedure called repeat_lyrics():
procedure repeat_lyrics(): print_lyrics() print_lyrics() end procedure
And then call repeat_lyrics():
repeat_lyrics() -- I'm a lumberjack, and I'm okay. -- I sleep all night and I work all day. -- I'm a lumberjack, and I'm okay. -- I sleep all night and I work all day.But that's not really how the song goes.
Pulling together the code fragments from the previous section, the whole program looks like this:
procedure print_lyrics() puts(1, "I'm a lumberjack, and I'm okay." ) puts(1, "I sleep all night and I work all day." ) end procedure procedure repeat_lyrics() print_lyrics() print_lyrics() end procedure repeat_lyrics()This program contains two procedure definitions: print_lyrics() and repeat_lyrics(). Routine definitions get executed just like other commands, but the effect is to create routine objects. The commands inside the routine do not get executed until the routine is called, and the routine definition generates no output.
As you might expect, you have to create a routine before you can execute it. In other words, the routine definition has to be executed before the first time it is called.
In order to ensure that a routine is defined before its first use, you have to know the order in which commands are executed, which is called the flow of execution. Execution always begins at the first command of the program. Commands are executed one at a time, in order from top to bottom.
Routine definitions do not alter the flow of execution of the program, but remember that commands inside the routine are not executed until the routine is called.
A routine call is like a detour in the flow of execution. Instead of going to the next command, the flow jumps to the body of the routine, executes all the commands there, and then comes back to pick up where it left off.
That sounds simple enough, until you remember that one routine can call another. While in the middle of one routine, the program might have to execute the commands in another routine. But while executing that new routine, the program might have to execute yet another routine!
Fortunately, Euphoria is good at keeping track of where it is, so each time a routine completes, the program picks up where it left off in the routine that called it. When it gets to the end of the program, it terminates.
What's the moral of this sordid tale? When you read a program, you don't always want to read from top to bottom. Sometimes it makes more sense if you follow the flow of execution.
Some of the built-in functions we have seen require arguments. For example, when you call sin() you pass a number as an argument. Some functions take more than one argument: power() takes two, the base and the exponent.
Inside the routine, the arguments are assigned to variables called parameters . Here is an example of a user-defined procedure that takes an argument:
include std/pretty.e procedure print_twice( object bruce ) pp(1, bruce ) puts(1, '\n' ) pp(1, bruce ) end procedureThis procedure assigns the argument to a parameter named bruce. When the procedure is called, it outputs the value of the parameter (whatever it is) twice.
When you create a routine, you think in terms of parameters:
When you use a routine, you think in terms of arguments:
This routine works with any value that can be output.
print_twice( "Spam" ) -- Spam -- Spam print_twice(17) -- 17 -- 17 include std/math.e print_twice( PI ) -- 3.14159265359 -- 3.14159265359The same rules of composition that apply to built-in routines also apply to user-defined routines, so we can use any kind of expression as an argument for print_twice():
print_twice( repeat("Spam",4) ) -- {"Spam", "Spam", "Spam", "Spam"} -- {"Spam", "Spam", "Spam", "Spam"} include std/math.e print_twice( cos( PI ) ) -- -1.0 -- -1.0The argument is evaluated before the procedure is called, so in the examples the expressions "Spam" and cos(PI) are only evaluated once.
You can also use a variable as an argument:
sequence michael = "Eric, the half a bee." print_twice(michael) -- "Eric, the half a bee." -- "Eric, the half a bee."The name of the variable we pass as an argument (michael) has nothing to do with the name of the parameter (bruce). It doesn't matter what the value was called back home (in the caller); here in print_twice(), we call everybody bruce.
When you create a variable inside a routine, it is local , which means that it only exists inside the routine. For example:
procedure cat_twice( sequence part1, sequence part2 ) sequence cat = part1 & part2 print_twice( cat ) end procedureThis procedure takes two arguments, concatenates them, and prints the result twice. Here is an example that uses it:
sequence line1 = "Bing tiddle " sequence line2 = "tiddle bang." cat_twice(line1, line2) -- Bing tiddle tiddle bang. -- Bing tiddle tiddle bang.When cat_twice() terminates, the variable cat is destroyed. If we try to print it, we get an error:
print cat -- Errors resolving the following references: -- catParameters are also local. For example, outside print_twice(), there is no such thing as bruce.
To keep track of which variables can be used where, it is sometimes useful to draw a stack diagram. Like state diagrams, stack diagrams show the value of each variable, but they also show the routine each variable belongs to. Again, this is a way to show how a program works, but on paper.
Each routine is represented by a frame. A frame is a box with the name of a routine beside it and the parameters and variables of the routine inside it. The stack diagram for the previous example looks like this:
The frames are arranged in a stack that indicates which routine called which, and so on. In this example, print_twice() was called by cat_twice(), and cat_twice() was called by main, which is a special name for the topmost frame. When you create a variable outside of any routine, it belongs to main.
Each parameter refers to the same value as its corresponding argument. So, part1 has the same value as line1, part2 has the same value as line2, and bruce has the same value as cat.
If an error occurs during a routine call, Euphoria prints the name of the routine, and the name of the routine that called it, and the name of the routine that called that, all the way back to main.
For example, if you try to access cat from within print_twice(), you get a "Errors resolving the following references":
Traceback (innermost last): File "test.py", line 13, in main cat_twice(line1, line2) File "test.py", line 5, in cat_twice print_twice(cat) File "test.py", line 9, in print_twice print cat NameError: name 'cat' is not defined
This list of routines is called a traceback. It tells you what program file the error occurred in, and what line, and what functions were executing at the time. It also shows the line of code that caused the error.
The order of the routines in the traceback is the same as the order of the frames in the stack diagram. The routine that is currently running is at the bottom.
There are two kinds of routines: procedures and functions. Procedures like print_twice(), perform an action but don't return a value. Functions always return a value, such as the math functions we have used. Just incase you are forgetful, I call them "fruitful" functions.
When you call a fruitful function , you always want to do something with the result; for example, you might assign it to a variable or use it as part of an expression:
atom x = cos( radians ) atom golden = ( sqrt(5) + 1) / 2If you call a "fruitful" function all by itself, and do nothing with it, you get an error message.
sqrt(5) -- function result must be assigned or used -- sqrt(5) -- ^Procedures might display something on the screen or have some other effect, but they don't have a return value.
That means you can't assign a procedure to a variable:
object result = print_twice( "Bing" ) -- Syntax error - expected to see an expression, not a procedure -- object result = print_twice( "bing" ) -- ^The routines we have written so far are procedures. We will start writing "fruitful" functions in a few chapters.
It may not be clear why it is worth the trouble to divide a program into routines. There are several reasons:
Routines are an example of "encapsulation." Without encapsulation, large programs become impossible to write.
Also, don't forget to save your program before you run it. Some development environments do this automatically, but some don't. In that case the program you are looking at in the text editor is not the same as the program you are running. Debugging can take a long time if you keep running the same, incorrect, program over and over!
Make sure that the code you are looking at is the code you are running. If you're not sure, put something like print "hello" at the beginning of the program and run it again. If you don't see hello, you're not running the right program!
Extra tabs and spaces are not significant in Euphoria. You may use this to advantage to format your code to make it easier to read.
As we first wrote the procedure:
procedure cat_twice( sequence part1, sequence part2 ) sequence cat = part1 & part2 print_twice( cat ) end procedureYou may add meaningful comments, arrange code to suit you taste, and even indent end procedure as a redundant way to show where the routine ends:
procedure cat_twice( sequence part1, -- left side should be capitalized sequence part2 ) -- right side ends in period sequence cat = part1 & part2 print_twice( cat ) end procedureBoth versions of cat_twice() work exactly the same; beause they are identical routines.
If you get too creative with formatting you can make the program harder to read. That is why writing and debugging is an art.