2.5.1  ITEM: 1. if and if–else statements

As in many languages, conditional execution is provided by the if statement.  This statement is supplied with a logical condition; if the condition is T, the rest of the if statement is executed, whereas if the condition is F, the rest of the if statement is ignored.  An example:


> if (2^2^2^2^2 > 10000) "exponentiation is da bomb!"


"exponentiation is da bomb!"


The only twist here, really, is that the condition must evaluate to a single value, i.e. a vector of size() == 1.  The if statement, in other words, is essentially a scalar operator, not a vector operator.  If you have a multivalued logical vector, you can use the any() or all() functions to simplify it to a single logical value.  Alternatively, the ifelse() function provides a vector conditional operation, similar to that in R.

It is worth exploring this twist with an example.  Suppose you have a variable x which ought to be equal to 3, and a variable y which ought to contain two values, 7 and 8.  You might expect to be able to write:


> if (x == 3 & y == c(7,8)) "yes!"


ERROR (EidosInterpreter::Evaluate_If): condition has size() != 1.


The error informs you that the size of condition is not equal to 1 (and that that is a problem).  The expression y == c(7,8) produces a logical vector with two values, the result of testing the first and second values respectively.  The & operator thus produces a two-valued logical vector as its result, and if is not happy about that.  To resolve this, you could use the all() function, or in many cases more appropriately, the identical() function.  See the Eidos manual for further discussion of this issue.

It is also worth noting that the condition for if does not need to be a logical value; a value of a different type will be converted to logical by coercion if possible.

Often you want to perform an alternative action when the condition of an if statement is F; the if–else statement allows this.  It is simplest to just show this with an example:


> if (2/2/2/2/2 > 10000) "division is da bomb!"; else "not so much."


"not so much."


Super simple, right?

2.5.3  ITEM: 3. semicolons and "null statements", ;

Every statement in Eidos must end with a semicolon (except compound statements, which end with a closing brace).  However, when you’re working interactively in EidosScribe, EidosScribe will add a trailing semicolon to your statements if necessary, just to make your life simpler.  So when you type:

> 1+1==2

what is really being evaluated behind the scenes is:

> 1+1==2;

When you’re not working interactively, semicolons are required, and if you forget, you will get an error, like this:


> 1+1==2


ERROR (Parse): unexpected token 'EOF' in statement; expected ';'


EOF stands for End Of File; it’s a standard way of referring to the end of an input buffer, in this case the line of input provided by the user for execution.

The simplest and shortest possible statement in Eidos is the "null statement", which consists of nothing but a semicolon:

;

This is not terribly useful, since it does nothing.

2.5.4  ITEM: 4. compound statements with { }

The other thing you might wonder about, regarding if statements, is: what if I want to perform more than one action in response to the condition being T or F?  This, then, is an opportune moment to introduce the concept of compound statements.  A compound statement is a series of statements (zero or more) enclosed by braces.  An example is worth a thousand words:


> if (1+1==2)

{

   x = 1;

   x = x + 1;

   x;

}

else

{

   "whoah, I'm confused";

}


2


Note that the input here is spread across multiple lines for clarity; all of this could be typed on a single line instead.  If entered as multiple lines, it cannot presently be entered in EidosScribe’s interactive mode because the if statement would stand on its own and be evaluated as soon as it was completed; instead, the full text would need to be entered in the script area on the left, selected, and executed.  All of the blue lines are user input, whereas the final line in black, 2, shows the output of the execution of the whole if–else statement; the if clause is executed, the calculations involving x are performed, and the final statement x; produces a result which is printed to the console as usual.

The way that x; results in output here might seem a bit surprising at first, but it is a consequence of the fact that the value of a compound statement is the value of the last statement executed within the compound statement; the values of the previous statements are discarded.

You can use a compound statement in any context in which a single statement would be allowed.  For example, compound statements are very commonly used with looping constructs.

2.6.1  ITEM: 5. while statements

A while loop repeats a statement as long as a given condition is true.  The condition is tested before the first time that the statement is executed, so the statement will be executed zero or more times.  Here is a code snippet to compute the first twenty numbers of the Fibonacci sequence:


> fib = c(1, 1);

while (size(fib) < 20)

{

   next_fib = fib[size(fib) - 1] + fib[size(fib) - 2];

   fib = c(fib, next_fib);

}

fib;


1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765


Its use of a while loop is optimal, because it ensures that if the fib vector is already long enough to satisfy the length condition size(fib) < 20, no further values of fib will be computed.  You could use this while loop to lengthen the fib vector on demand within a larger block of code that used the fib vector repeatedly.

2.6.2  ITEM: 6. do–while statements

A do–while loop repeats a statement as long as a given condition is true.  Unlike while loops, in this case the condition is tested at the end of the loop, and thus the loop statement is always executed at least once.  Here is a code snippet to compute a factorial:


> counter = 5;

factorial = 1;

do

{

   factorial = factorial * counter;

   counter = counter - 1;

}

while (counter > 0);

"The factorial of 5 is " + factorial;


"The factorial of 5 is 120"


Note that this example could be rewritten using a while loop instead, but it might be a bit less intuitive in its operation since it would no longer embody the formal definition of the factorial as explicitly.  Note also that computing a factorial could be done much more trivially (and efficiently) using the sequence operator : and the product() function, but the code here is useful for the purpose of illustration.

2.6.3  ITEM: 7. for statements (with in)

The for loop is used to loop through all of the elements in a vector.  For each value in the given vector, a given variable is set to the value, and a given statement is then executed.  For example, the following code computes squares by setting element to each value of my_sequence, one by one, and then executing the print() function for each value:


> my_sequence = 1:4;

for (element in my_sequence)

   print("The square of " + element + " is " + element^2);


"The square of 1 is 1"

"The square of 2 is 4"

"The square of 3 is 9"

"The square of 4 is 16"


This looping construct is called by various names in other languages, such as the “for each” statement (PHP), the “range-based for” (C++), “fast enumeration” (Objective-C), and so forth.  It is different from the traditional for loop of C and related languages, which entails an initializer expression, a condition expression, and an increment/decrement expression.  That type of for loop does not exist in Eidos (following R); the iterator for of R and Eidos is a more natural and efficient choice for vector-based languages.

2.6.4  ITEM: 8. next statements

Sometimes you might wish to cut short the execution of a given iteration of a loop, skipping the rest of the work that would normally be done and proceeding directly to the next iteration.  This is the function of the next statement.  The next statement can be used within for, while, and do–while loops.

2.6.5  ITEM: 9. break statements

Often it is necessary to stop the execution of a loop altogether, not just to cut short the current iteration of the loop as next does.  To achieve this – to break out of a loop completely – use the break statement.  The break statement can be used within for, while, and do–while loops.

2.6.6  ITEM: 10. return statements

The return statement returns a value from a block of code, as in other languages such as C and R.  In one common case, when defining a user-defined function, return is used to stop execution of the function and return a given value to the caller.  Otherwise, a return is useful mostly when the Context within which you’re using Eidos uses the returned value.  When using Eidos in SLiM, for example, SLiM uses the value returned by Eidos scripts such as mutationEffect() callbacks and mateChoice() callbacks, making return very useful in that Context.  Apart from such Context-dependent uses, return is mainly useful as a way to break out of nested loops regardless of the depth of nesting, as illustrated below.

The return statement is very simple: the keyword return, and then, optionally, an expression.  When the return statement is executed, the expression is evaluated and its value is immediately returned as the value of the largest enclosing statement.  The return statement therefore breaks out of all conditionals, loops, and compound statements, regardless of the depth of nesting.

In some circumstances a return statement is not necessary, because compound statements evaluate to the value of the last statement evaluated within them, and if statements behave similarly; as in R, therefore, a return statement can often be omitted.  However, using return makes the intentions of the programmer more explicit, and so its use is encouraged.

If the expression for the return statement is omitted, the return value used is NULL.  In situations where the return value will not be used, such as Eidos events in SLiM, the return value should be omitted to make the intent of the code clear.

2.9.1  ITEM: 11. single-line comments with //

Technically, comments are actually a type of whitespace; comments in Eidos code are completely ignored and have no effect whatsoever on the results of the execution of code, just like other kinds of whitespace in most respects.  Single-line comments begin with // and then may consist of any text whatsoever, up to the end of the current line of code.  A comment may occur by itself on a line, or it may follow other Eidos code.  So for example, you could write:

1 + 1 == 2;    // this is true

Comments are never required in Eidos, but using them to annotate your code is nevertheless a very good idea, both so that you remember what your intentions were when you come back to the code weeks or months later, and so that others who might need to understand or maintain your code have a helping hand.

2.9.2  ITEM: 12. block comments with /* */

It is possible to comment out whole blocks of script, instead of just single lines.  This can be useful for writing longer comments that describe a section of code in more detail.  In Eidos (as in C and C++), such block comments can be written with a beginning /* and a terminating */.  Here’s an example of this style of comment:

/*

   This computes the factorial x!, which is

   the product of all values from 1 to x, for

   any positive integer x.

*/

x_factorial = product(1:x);

A nice feature of Eidos is that block comments nest properly, making it possible to use them to comment out stretches of code that already contain block comments.  For example, if the code above was no longer needed, but you didn’t want to delete it entirely because you might need it again later, you could use a block comment to disable it:

/* ******* NOT NEEDED ************

/*

   This computes the factorial x!, which is

   the product of all values from 1 to x, for

   any positive integer x.

*/

x_factorial = product(1:x);

*/

The outer block comment is not terminated by the first */ because Eidos recognizes that that belongs to the inner block comment; the outer block comment continues until the second */ is encountered.

4.1  ITEM: 13. user-defined functions

Suppose we wish to define a function that doubles whatever float value is passed to it.  This is very easy to do:

function (float)double(float x)

{

return 2 * x;

}

The function keyword initiates the declaration of a new function.  It is followed by the full signature for the new function; here the signature declares that the function is named double, takes a parameter named x that is of type float, and returns type float.  This signature is then followed by the definition of the new function, in the form of a compound statement; here, the double() function is defined as returning two times the value it was passed.  Note that a return statement is used here to return a specified value from the function; if no return statement is encountered, the value of the last statement evaluated is automatically returned to the caller (as in R), but generally it is clearer to explicitly use return.

Calling such functions works in exactly the same way as calling built-in functions:

> double(5.35)

10.7

Functions may be recursive; a simple factorial() function might be defined recursively as:

function (integer)factorial(integer x)

{

if (x <= 1)

return 1;

else

return x * factorial(x - 1);

}

This works well enough, as you can see:

> factorial(13)

6227020800

As with the built-in Eidos functions, user-defined functions may take multiple parameters, each of which may be allowed to be one of several different possible types.  Parameters to user-defined functions may also be optional, with a default value if left unsupplied.  Finally, functions are scoped; the code inside them executes in a private namespace in which only the parameters to the function are available, and variables defined inside a function will not persist beyond the end of the function’s execution.