Methods, Functions and Statements

Methods and Functions

Methods and functions are quite similar both in appearance and, if you'll pardon the pun, function. Methods and functions are both containers for blocks of code, that is, for a series of statements, or instructions to the computer to carry out the commands the method or function defines. The principle difference between them is that a method belongs to an object, whereas a function is free-standing. For example, in the Heidi example we developed in the previous chapter, afterAction() was a method of the branch object, while finishGameMsg() was a function defined in the adv3Lite library (in fact, in this particular instance, precisely the same function is defined in the adv3 library too).

The syntax for defining a function is:

functionName ( [ paramName [ , paramName ... ]  ]  )
{
   functionBody
}

This means that to define a function you start with the function name, then put an opening parenthesis, then a list of parameters the function takes, then a closing parenthesis, following which you write the the body of the function between opening and closing braces. To give a couple of more concrete examples:

sayHello()
{
   "Hello World!";
}

double(x)
{
   return x * 2;
}

The sayHello() function simply displays the text "Hello World!" when it is invoked. The double() function accepts a single number as a parameter and returns double that number. It could be used like this:

  y = double(3);

Which would result in y containing the value 6. (In this code fragment, y is a variable; we'll explain variables in the next subsection).

The sayHello() example incidentally shows that if you want to define a function (or method) that takes no parameters, you simply follow its name with a pair of parentheses. A parameter is simply a temporary storage area (just like a variable) that you can use to pass a value to a function or method. In the double() example, calling double(3) causes the x parameter to take the value 3, so that when double(3) executes the statement return x * 2; it first multiplies 3 by 2 (since x now has the value 3) and then returns the result (i.e. 6) to the caller.

Incidentally, you'll often come across parameters referred to as arguments. Technically the two terms mean something slightly different: parameters are what you define when you define the function or method, arguments are what you pass when you call the function or method, but for most practical purposes this is a distinction that seldom matters, and if you refer to both parameters and arguments as 'arguments' you probably won't get into much trouble!

A method is defined almost exactly like a function, except that its definition occurs within the body of an object or class. For example:

class Greeter: Thing
   greet()
   {
      "<<name>> says hello! ";
   }
;

bob: Greeter 'Bob; tall; man; him'
  "He's a very tall man. "
  
  doSum(x, y)
  {
     "<q>Let me see,</q> says Bob. <q>By my reckoning,<<x>> plus <<y>> is
       <<x + y>>. ";
  }
;

In this somewhat contrived example, the Greeter class defines a greet() method, which the bob object inherits (since it's of class Greeter). Calling bob.greet() would result in the display of "Bob says hello!" while calling bob.doSum(10, 5) would result in the display of '"Let me see," says Bob, "By by reckoning 10 plus 5 is 15"'.

In this example bob gets its name property via the vocab property we defined using the Thing template (since Greeter inherits from Thing, we can still use the Thing template with it). This incidentally illustrates another important difference between a method and a function. A method can refer to other properties and methods of the object on which it's defined. A function can't do this because it isn't part of any object. Likewise a method can use the special keyword self to refer to the object on which it's defined; a function can't do this since there would be no object for self to refer to, a function not being part of any object.

By the way, these examples have all been of very simple methods and functions with a single line of code (in fact, a single statement). In fact a function or method can contain as many statements as you like (although if a function or method starts to grow too big that may be a sign that it should be broken up into a number of functions or methods). We have simply kept these examples as simple as possible to illustrate the basic principles of defining functions and methods.

Variables

A variable is simply a temporary container for a piece of information, such as a number, a piece of text, a list, a reference to an object, or a special value like true or nil. In TADS 3 all variables are local, which means that they can only exist within a function or method, and only keep their value for as long as that function or method is being executed. In fact, the lifetime of a TADS 3 variable may be even less than that, for a variable only persists for as long as the block in which it is defined is being executed, where a block is any set of statements enclosed in a matching pair of opening and closing braces ({}). A method or function is simply the outermost possible kind of enclosing block.

A variable must be declared before it can be used. A variable is declared using the keyword local, followed by one or more variable names (separated by commas, if there's more than one). A variable may optionally be initialized at the same time it is declared by following it with an equals sign followed by whatever value you wish to give it. If a variable is declared but not initialized it will have the value nil (i.e. nothing at all) until it is assigned a value. The following are all examples of legal variable declarations:

  local a;
  local numberOfEggs, weightOfFlour, quantityOfButter;
  local price = 10, weight, title = 'cake recipe';

On the whole it's best to give your variables meaningful names as in the second and third examples above, since this will make your code easier to read (and maintain). For some purposes (such as a loop counter), a brief single-letter variable name may suffice, however. A variable name should be unique in the block of code in which it occurs, and you should try to avoid using the name of an object, method, function or other identifier as a variable name since this is likely to confuse the compiler (as well as you) and would probably lead to compilation errors.

On the other hand, it's quite okay to use the same variable name in different functions and methods. If you define a local variable called title in a function called showName() and another local variable called title in a method called mixCake() the two title variables will have no relation to each other at all, and no confusion will occur.

As its name suggest, a variable is something whose value can vary as your code is executed. Consider the following code snippet:

someFunction()
{
   local myVar = 2;
   myVar = myVar + 2;  // now myVar is 4
   myVar = 'Hello';    // now myVar is 'Hello'
   myVar += ' World!'; // now myVar is 'Hello World!'   
}

Statements

With one or two exceptions, we were able to write almost the whole of The Adventures of Heidi in the previous chapter using purely declarative programming, that is by defining objects and declaring the values of certain of their properties. Although adv3Lite tries to let you write your game as far as possible this way, as we've already seen in the code for ending the Heidi game, you can't do everything that way, and the more original, interesting and sophisticated you want your game to be, the more you'll need to supplement your declarative code with procedural code, that is code that consists of a set of instructions (i.e. a procedure) telling your game what to do under certain circumstances. Such code is made up of individual statements.

A statement is simply an instruction to your game to carry out a particular task, expressed in the TADS 3 programming language. With one or two exceptions that need not concern us here, statements can only occur within a function or method (one obvious exception would be statements that define (the header of a) function or method. In this section, however, we shall be entirely concerned with procedural statements, that is the kind of statements that can be put inside a method or function to make it carry out a particular procedure.

The kinds of procedural statements you'll most commonly used are:

As we'll see, some of these kinds can be combined into a single statement, but for convenience of presentation we'll take each kind in turn. But one feature all procedural statements have in common is that they must be terminated with a semicolon.

Declarations

A declaration statement is one that declares a new local variable, as we have already seen above. The following statement declares the local variable myNewVar:

local myNewVar;

As already noted, a local variable must be declared before it can be used, but a variable can be declared and assigned an initial value in the same statement, for example:

local myNewVar = 10;
local someonesName = 'John Doe';
local obj = bird;
local bool = true;

The above example could alternatively be written all as one statement:

local myNewVar = 10, someonesName = 'John Doe', obj = bird, bool = true;

Assignment Statments

An assignment statement assigns the value of an expression to a local variable or object property. It has the form:

  lvalue = expression;

Where lvalue is the property or variable to which the value is being assigned, and expression is any valid TADS 3 expression. Examples of assignment statements include:

  x = 2;
  x = x + 3;
  obj = bird;
  obj.name = 'parrot';
  str = 'Hello' + ' World';
  val = double(x + 1) / 3 + 18;  // val is now 22

An expression can be any legal combination of variables, property names, function and/or method calls, and operators. Operators include the common arithmetic operators + - / * (the last two of which are used for division and multiplication), the string concatenation operator +, the logical operators && || and ! (and, or and not), and the comparison operators == != > < >= and <= (equals, not equals, less than, greater than, greater than or equal to, and less than or equal to).

NOTE. Just as in C (whose syntax TADS 3 borrows to a large extent) be very careful not to confuse the assigment operator = with the test for equality operator ==. Consider the following:

  a = 3; // Assigns the value 3 to a.
  a == 3; // Tests whether a is 3 (and here evaluates to true), but doesn't do anything.
  
  if(a = 4)   // legal but doesn't do what you probably expect 
     say ('a is 4!')

The statement a == 3 is a legal statement because a statement can consist just of an expression, even if the expression doesn't do anything as here. The test if(a = 4) is legal, because an assignment like a = 4 is also an expression (it evaluates to 4). But since 4 is considered a true value (being neither zero nor nil), the test will be passed if a is any number other than zero, causing 'a is 4!' to be displayed whatever the value of a (other than when a is zero or nil).

There are also various short form assignment statements. Because it's so common to write statements like:

  count = count + 2;

This can be abbreviated to:

  count += 2;

And so on with similar operators like -= *= and /=. Because adding or subtracting 1 from a number is so common, this can be abbreviated even further:

  
  count++; // equivalent to count += 1;
  count--; // equivalent to count -= 1;  
  ++count; // equivalent to count += 1;
  --count; // equivalent to count -= 1;

The difference between ++count and count++ is simply the point at which the incrementing of count occurs. This can be illustrated briefly like this:

  count = 0;
  a = count++ // a is now 0 and count is now 1, because count is incremented after its value is assigned to a
  b = ++count // b and count are now both 2, because count is incremented before its value is assigned to b

Method and Function Calls

As we have seen, an expression by itself can constitute a perfectly valid statement. Often it would be a pointless statement; the expression count + 1; is valid as a statement, but it doesn't actually do anything (in particular, it doesn't increase the value of count by 1). But expressions that consist of method or function calls are often useful as statements, because of their so-called side-effects. "Side-effect" is actually a slightly odd term here, because what we mean is what the method or function actually does. Examples we have actually seen include:

  finishGameMsg(ftVictory, [finishOptionUndo]); // ends the game
  bird.moveInto(nest); // moves the bird into the nest

As you come to write more TADS 3 code (whether in adv3Lite or adv3) you'll probably find yourself using this kind of statement a lot.

Flow Control Statements

The statements we have discussed so far allow you to write methods and functions that do things, but they don't give you much flexibility or control. The power of any computer language comes from its ability to take different routes through the code depending on circumstances, and that's every bit as important when writing Interactive Fiction as it is for other kinds of application.

One of the most common — and most important — statements is the if statement. We have already met it (in the afterAction() method of the branch object) in the form:

if ( conditionExpression )
  thenPart

Where span class=synPar>conditionExpression is an expression that typically evaluates to either true or nil and thenPart is either a single statement or a block (enxlosed by opening and closing braces) containing multiple statements. For example:

+ branch: Thing 'wide firm bough; flat; branch'
    "It's flat enough to support a small object. "
    
    iFixed = true
    isListed = true
    contType = On
    
    afterAction()
    {
        if(nest.isIn(self))
            finishGameMsg(ftVictory, [finishOptionUndo]);
    }
;

There's also a secong form of the if statement that looks like this:

if ( conditionExpression )
  thenPart
else
  elsePart 

If this form of the if statement, both thenPart and elsePart may be either a single statement or a block of statements. If conditionExpression evaluates to anything but nil or 0, then thenPart is executed, but if conditionExpression does evaluate to either nil or 0 then elsePart is executed. For example;

 if(obj.weight > 50)
 {
    "You can't budge it. ";
 }
 else
 {
    obj.moveInto(crevasse);
    "You push <<obj.theName>> over the edge, and it tumbles away out of sight. ";
 }
 

In this example, if obj had a weight of 51, say, then the player would see the "You can't budge it" message, but if it had a weight of 50, obj would be moved into the crevasse and the player would see the message about it tumbling out of sight.

Another common flow-control statement is the return statement, which terminates the execution of a function or method. This has two forms:

 return;
 return expression;
 

The first form simply terminates the function or method. The second terminates it and returns the value of expression to the caller. For example:

 
 absolute(x)
 {
    if(x < 0)
       return -x;
       
    return x;
 }
 
 sayDivide(x, y)
 {
    if(y == 0)
    {
       "You can't divide by zero. ";
       return;
    }
    
    "<<x>> divided by <<y>> is <<x/y>>. ";
    return;
 }
 
 ...
 
   a = absolute(-40) // a is now 40
   a = sayDivide(a, 20) // a is now nil
 

In the second case, although the player would see the message "40 divided by 20 is 2", since the function returns no value, a will end up as nil.

The third common type of flow-control statement is the loop statement, of which TADS 3 provides several. The most versatile (and hence common) of these is the for statement, which can take several forms:

for ( [ initializer ]  ; [ condition ]  ; [ updater ]  )
  loopBody

The initializer is either an ordinary expression, or a list of local variable declarations, or a mix of both:

( expression | local varName = expression )  [ , ... ] 

An example of this kind of for loop would be:

  local count = 0;
  for(local i = 1; i <= 10; i++)
     count += i;  
 

This would sum the numbers from 1 to 10 and store the total (55) in count.

The second form of the for loop is:

for ( [ local ]  loopVar in expression )
   loopBody

In this form of the for loop, expression would typically be a list (or an expression that evaluates to a list). For example, to calculate the total bulk of the items carried by Heidi (perhaps in a game where we didn't restrict her carrying capacity quite so much) we might use:

local totalBulk = 0;
for(local item in heidi.contents)
   totalBulk += item.bulk;

The third form of the for loop is:

for ( [ local ]  loopVar in fromExpr .. toExpr [ step stepExpr ]  )
   loopBody

Where fromExpr, toExpr, and the optional stepExpr are expressions that evaluate to integer values. If there's no step clause, the default step value is 1. With this form of the loop the first example (summing the numbers from 1 to 10) could have been written as:

local count = 0;
for(local i in 1..10)
  count += i;
 

As an added bonus, we can combine these different forms of for loop into a single statement. For example, the following could be used to display an enumerated list of Heidi's possessions:

"Heidi is holding:\n";

for(local item in heidi.contents, local i = 1 ;; i++)
   "<<i>>. <<item.aName>>\n";
 

These are not the only kind of loop (and other flow-control) statements that TADS 3 provides, but they are the ones that are most commonly used. We'll explain any others that arise if and when we come to them.

Double-Quoted String Statements

Although we've already used several examples of it, we should complete this review of statement types by mentioning the double-quoted string statement. This is simply an instruction to display some text to the player, and takes the form of placing the text between double quote marks, and ending the statement with a semicolon:

 
"This text will be displayed to the player. ";

The same result can be achieved via a function call:

say('This text will be displayed to the player. ');

But the double-quoted string statement form is often more convenient. Note that in this statement the text to be displayed can contain embedded expressions, that is expressions enclosed in double angle-brackets <<>>. So for example we could write:

"Heidi is carrying <<heidi.contents.length>> things right now. ";

The above example won't read to well if Heidi is carrying just one thing, and may not be ideal if she is completely empty handed, but we can also use embedded expressions to change what's displayed according to various conditions, for example:

local numItems = heidi.contents.length;
"Heidi is <<if numItems == 0>> empty-handed<<else if numItems == 1>>carrying just one thing<<else>> carrying <<numItems>> things<<end>>. ";

Embedded expressions can also be used to vary what's displayed either randomly or sequentially, for example:

"Heidi is very<<one of>>sad <<or>>happy <<or>>energetic <<or>>tired <<shuffled>> today. ";
"Heidi is very<<one of>>sad <<or>>happy <<or>>energetic <<or>>tired <stopping>> today. "

The first statement will run through 'sad', 'happy', 'energetic' and 'tired' in random order, then shuffle the order and repeat through again in the new order and so on. The second statement will run through 'sad', 'happy', 'energetic' and 'tired' in that order and then keep repeating 'tired'. For a full list of the embedded expressions you can use, see the section on "String Literals" in Part III of the TADS 3 System Manual.

Further Reading

We have covered a lot of ground in rather a compressed manner in this section. Don't worry too much if it doesn't all make perfect sense yet, we'll be explaining many of the new features again when we use them in the games we'll be looking at. To get the full story on the material we have just sketched above, see the section on "Procedural Code" in Part III of the TADS 3 System Manual. You might also find it helpful to look at the sections on "Fundamental Datatypes" and "Expressions and Operators" in the same part of the System Manual. If you're at all unsure about the material we've just covered, or you're interested in going into more detail to get the full picture, now might be a good time to read these three sections. On the other hand, if you're comfortable with what we've just covered in this section and would like to carry on reading the next straight away, that's fine too, but I would still recommend that you read those three sections of the TADS 3 System Manual sooner or later.