FANDOM


Lua is the language used for recipes (also known as scripts or macros) in Foldit. See Lua Scripting for an overview and links to series of beginner scripting tutorials.

This tutorial is for more experienced programmers, giving some information about functions and tables in Lua.

The lighter-colored boxes on this page mostly contain Lua code, for example:

print ( "Sample Lua recipe output" )

In most cases, you should be able to copy the code, paste it into the Foldit recipe editor, and run it. (Some of the examples may be incomplete code fragments, compare them to previous examples if in doubt.)

The darker-colored boxes contain sample output:

Sample Lua recipe output

Functions

If you already did a shake, wiggle or just printed out a text, you've already used or "called" a function. In addition to just using them, you can create your own functions.

Using and declaring

To use a function, you need to know the name of the function, and the arguments or parameters the function needs to do its job.

Here's a simple example:

print ( "Hello" )

Here, we call the function named print with only one argument - the constant expression "Hello".

The function name is: print

The function argument or parameter is: "Hello"

Let's now feed a variable into print:

outputtext = "Hello"
 
print ( outputtext )

In this example, print gets the variable outputtext as argument. The result is exactly the same as the previous example.

The function "print" is pre-defined. It is time to create our own function:

function print2 ( a, b )
    print ( "A contents: " .. tostring ( a ) )
    print ( "B is: " .. tostring ( b ) )
end

a = "Hello"
b = "World"
 
print2 ( a, b )

Here, we defined a new function print2, with two arguments, a and b. The definition of print2 runs from the function statement to the matching end statement. By itself, the definition doesn't do anything, we still need to call the function at the end of the example.

Inside our function, we call the function print twice, to show the content of each of the arguments.

In these examples, the Lua concatenation operator ".." is used to put together parts of the message. The result is a single string that gets passed to print. This produces better looking results than passing multiple arguments separated with commas.

Just to be on the safe side, the function tostring is used to make sure that the values of a and b become printable strings. Lua automatically converts numeric variables to strings as needed. But if a variable has the special value nil, concatenation will fail without the tostring. Also, a true/false boolean variable needs tostring before its value can be concatenated to a string.

To remember, we could also call our function with constants:

function print2 ( a, b )
    print ( "A contents: "  .. tostring ( a ) )
    print ( "B is: " .. tostring ( b ) )
end

print2 ( "Hello", "World" )

This would give the same result as the previous example.

Aliases of variables

By using arguments, you don't need the same names for your variables within functions:

function print2 ( c, d )
    print ( "A contents: " .. tostring ( c ) )
    print ( "B is: " .. tostring ( d ) )
end
 
a = "Hello"
b = "World"
 
print2 ( a, b )

Compare this code with the previous example. It does the same job, but we changed the variable names in the function section.

We still feed the variables a and b as arguments into the function in this code section:

a = "Hello"
b = "World"
 
print2 ( a, b )

But in our function declaration, what was called a is now c, and 'b is now d.

The text to print is still the same in this part of the code:

function print2 ( c, d )
    print ( "A contents: " .. tostring ( c ) )
    print ( "B is: " .. tostring ( d ) )
end

This example shows that the variables used when defining a function aren't necessarily the same as the variable names used when you call the function. This applies to variables that are passed as an argument to a function. Different rules apply to variables inside a function that aren't arguments. For those variables, it's important to understand their scope, and whether they're global or local variables. The next section discusses the scope of variables.

Scope of global and local variables inside and outside

The term scope is used to talk about where a given variable is available in a program. Visibility is another way to talk about the same idea. In Lua, all variables have global scope by default, meaning they are available everywhere, inside of any function. There are just a few exceptions:

  • variables mentioned in a function's argument list are local to the function
  • the variable used as the index to a for loop is local to the for loop
  • variables declared with the local keyword are local to the scope where they were declared

Local variables disappear or "go out of scope" and aren't always available. For example, in last example of the print2 function, the variables c and d are local to the function. They available only between the function statement and the matching end.

In the example, outside the print2 function, the values are still stored in the variables a and b. But the names c and d don't mean anything outside of print2.

Let's see a simplified example, using a variable explicitly defined as local:

function test ()
    local a = 5
    print ( "A at function call is: " .. a )
end
 
print ( "A before function call is: " .. tostring ( a ) )
 
test ()
 
print("A after function call is: " .. tostring ( a ) )

This should result:

A before function call is: nil
A at function call is:  5
A after function call is: nil

In Lua, variables which haven't been given a value have the special value nil. In this example, outside of the function test, the variable a hasn't been defined. We can still use a, but it has the value nil. This variable a ends up with global scope.

(As discussed earlier, the function tostring is needed to correctly print nil values.)

Inside the function test, there's also a variable called a which is given a value. This variable a is different than the global a defined outside the function. This local a hides the global a for the duration of the function.

After leaving the function, the global variable a is used again, and it still has a nil value.

Let's see what happens if we remove the local from the definition of a inside the function:

function test ()
    a = 5
    print ( "A at function call is: " .. tostring ( a ) )
end

print ( "A before function call is: " .. tostring ( a ) )

test ()

print ( "A after function call is: " .. tostring ( a ) )

The output is now:

A before function call is: nil
A at function call is: 5
A after function call is: 5

At start of this example, a is again created when it's used on the first print statement, and again is nil. Inside the function, the keyword local is missing this time, so no new variable a is created. Instead, a refers to the global a, created outside the function.

In this case, the name a refers to the same variable inside and outside the function. The function sets a to 5, and the change is still visible after the function ends.

Global variables are handy, but they can get confusing as your Foldit recipe gets larger. In other programming languages, scope works the opposite way: variables are local unless you make them global. Lua is aimed more at small- to medium-sized scripts or macros, so variables are global by default.

Global variables can cause problems. To see how, let's start with a problem-free example:

function count3 ()
    local i = 0
    while i < 3 do
        i = i + 1
        print ( "\"i\" inside function is: " .. tostring ( i ) )
    end
end

local i = 0
while i < 2 do
    i = i + 1
    print ( "\"i\" outside function is: " .. tostring ( i ) )
    count3 ()
end

This should result:

"i" outside function is: 1
"i" inside function is: 1
"i" inside function is: 2
"i" inside function is: 3
"i" outside function is: 2
"i" inside function is: 1
"i" inside function is: 2
"i" inside function is: 3

In this example, there are two loops, one outside the function, and one inside the function.

The outside loop counts 1 to 2 and calls a function named count3.

In count3, there's a loop counting from 1 to 3.

Both loops are using a variable named i to count, but the i inside the function is explictly defined as local, so they're really two separate variables.

No problem so far.

Let's see what happens if we remove the keyword local:

function count3 ()
    i = 0
    while i < 3 do
        i = i + 1
        print ( "\"i\" inside function is: " .. tostring ( i ) )
    end
end

i = 0
while i < 2 do
    i = i + 1
    print ( "\"i\" outside function is: " .. tostring ( i ) )
    count3 ()
end

(Notice that we're using \" to escape the double quotes in the print statements. This is how you can get double or single quotes into print output, since otherwise Lua interprets these characters as programming statements.

The output is now much shorter:

"i" outside function is: 1
"i" inside function is: 1
"i" inside function is: 2
"i" inside function is: 3

In this example, the same variable i is used both inside and outside the function. The changes to i inside the function overwrite the value used outside the function. The outer loop terminates after the first time through, since the value of i is now 3.

As mentioned earlier, the index variable of a for loop is automatically defined as a local variable. If we rewrite the example using for loops, there's no need to make things local:

function count3 ()
    for i = 1, 3 do
        print ( "\"i\" inside function is: " .. tostring ( i ) )
    end
end

for i = 1, 2 do
    print ( "\"i\" outside function is: " .. tostring ( i ) )
    count3 ()
end

This example produces the same output as the first "problem-free" version.

As a more extreme example, the function count3 could even be eliminated:

for i = 1, 2 do
    print ( "\"i\" outside function is: " .. tostring ( i ) )
    for i = 1, 3 do
        print ( "\"i\" inside function is: " .. tostring ( i ) )
    end
end

This version again produces the "problem-free" output. It works because the outer for loop automatically defines i as being local. The inner for loop also defines a local i, which masks the i used by the outer loop (and any global i that might be around). Once the inner loop is done, its version of i goes out of scope, and the i used by the outer loop is visible again.

The extreme example can be a little confusing, and you won't often see it. It's more likely to happen by accident. For example, if the outer loop contains a lot of code, you might forget that you're already using i, and use it again to index an inner loop. While this can be OK, as the example shows, it's probably better to use different names.

The extreme example becomes less extreme if you use different names for the loop indexes:

for ii = 1, 2 do
    print ( "\"ii\" outside function is: " .. tostring ( ii ) )
    for jj = 1, 3 do
        print ( "\"jj\" inside function is: " .. tostring ( jj ) )
    end
end

This version makes what's going on much clearer. The loops are indexed separately, so if the inner loop needs to reference whatever the outer loop is indexing, it can. In Lua, loops are often processing tables, which are discussed below.

The no-longer-extreme example also doubles up the index names, so we have ii and jj instead of i and j. This can be a little clearer, and depending on your editor, it may be easier to find ii than just i. Use of i, j, and k probably goes back to Fortran, where variables that began with these names were integers by default.

In general, the best scoping advice is to explicitly add local to variable definitions, unless you want them to be global. Many Lua recipes in Foldit put global variables at the beginning just to make it clear that they're intended to be global. Putting globals up front and giving them initial values is not required, but it can help eliminate a lot of confusion.

In the next section, we'll look at some ways that careless use of variable names can cause problems.

Accidentally created new variables and debugging

Since Lua makes variables global by default, it's easy to accidentally create and use global variables without knowing it.

In most "scripting" languages like Lua, you don't need to define variables before you use them. Some "programming" languages are different, requiring you to explicitly give a variable a type and scope before you can use it.

In Lua, if you mistype a variable name, you accidentally create a new global variable with nil value.

You won't see the problem until you use the new value. The value nil can't be compared with variables containing actual values.

To see what happens, let's look at a problem-free example first:

function PlusOne ()
    print ( "a + 1 is: " .. tostring ( a + 1 ) )
end
a = 1
PlusOne ()

The result is:

a + 1 is:  2

The content of the global variable a is also visible in the function, so we can add 1 to it in the print statement.A

If we mistype the name of the variable as a capital:

function PlusOne ()
    print ( "a + 1 is: " .. tostring ( A + 1 ) )
end
a = 1
PlusOne ()

It gives the following error:

ERROR: [string "function PlusOne()..."]:2: attempt to perform arithmetic on global 'A' (a nil value)

Lua is case-sensitive, so A is not the same as a.

Errors of this type terminate a recipe, but they're usually easy to track down. The tricky part is that the error doesn't occur until you call the PlusOne function, so your recipe can go on for quite a while before crashing.

Returning values

As we've seen in the previous examples, a function can accept values as arguments, and it can also use global variables.

The next step is how to get values back out of a function. A function can put its results into global variables, but this can cause problems if you're not extremely careful.

The preferred approach is to use the return statement. Lua allows a function to specify one or more values on a return statement. These returned values are then available to the function's caller, but not elsewhere.

Returning values is logically similar to passing them to a function as arguments. It's a private conversation between the function and its caller in both cases.

Here's an example that skips the use of global variables, using the return statement instead:

function increase ( x )
    local x = x + 1
    return x
end
a = 1
print ("\"a\" before increasing is: " .. tostring ( a ) )
a = increase ( a )
print ( "\"a\"A after increasing is: " .. tostring ( a ) )

This example works, but it might be a little confusing. The increase function receives an argument x, which becomes a local variable visible inside the function. The very first line of the functions creates another local variable x, which replaces the argument x in the rest of the function. This only works because the right-hand side of the equal sign gets processed first!

A shorter and less confusing form is:

function increase ( x )
    return x + 1
end
a = 1
print ( "\"a\" before increasing is: " .. tostring ( a ) ) 
a = increase ( a )
print ( "\"a\" after increasing is: " .. tostring ( a ) )

This version skips over creating a new variable to receive the result of the function. Instead, it calculates the new value directly on the return statement. This approach also avoids having two different values named x in the same function.

As mentioned, Lua lets you return more than one value on a return statement. This feature makes Lua stand out from many other languages, which typically involve only one return value.

For example:

function doubler ( x, y )
    return 2 * x, 2 * y
end

local m = 4
local n = 5

local twom, twon = doubler ( m, n )

print ( "m = " .. tostring ( m ) .. ", 2 x m = " .. tostring ( twom ) )
print ( "n = " .. tostring ( n ) .. ", 2 x m = " .. tostring ( twon ) )

returns the output:

m = 4, 2 x m = 8
n = 5, 2 x m = 10

On the call to doubler, the local applies to both twom and twon, which are used to receive the two values returned by the function.

Tables

Tables are the most powerful part of Lua. Tables are like arrays in other languages, but are much more flexible. Many languages restrict arrays to having just one type of value. Lua's flexible typing means a table can hold any type of data.

Table is one of the data types in Lua, so a table can contain other tables.

Simple table

Here's an example of a simple table:

local tab1 = { "a", 2, "beta", 4, false, }

for ii = 1, #tab1 do
    print ( "entry " 
                .. ii .. 
            ", type = " 
                .. type ( tab1 [ ii ] ) .. 
            ", value = "
                .. tostring ( tab1 [ ii ] )
          )
end

In the example, tab1 is defined as a one-dimensional table with values of different types. The curly braces { and } indicate the start and end of the table. Commas separate the values. There's even a comma after the last value, which is not required, but can be helpful if you want to add values.

The for loop in the example prints out the table. The expression #tab1 means "number of entries in tab1". The # works for tables which have numerically indexed values. Not all Lua tables work this way.

The square brackets [ and ] are used to refer to the values in the table.

In the for loop, the Lua function type returns the data type of the argument. As in previous examples, the Lua function tostring is used to make sure we have a printable string value.

The print statement itself uses the .. concatenation operator as in previous examples. Here, the print statement is more complex, so it's spread across several lines for clarity.

This example produces the output:

entry 1, type = string, value = a
entry 2, type = number, value = 2
entry 3, type = string, value = beta
entry 4, type = number, value = 4
entry 5, type = boolean, value = false

In Lua, tables of this type start with index 1. Other languages, use an offset instead of an index, and arrays start at offset 0.

Both "a" and "beta" are strings, and could also be written as 'a' and 'beta'. In Lua, there's no difference between single-charcter and multiple-character values. The empty string is "" or ''.

The words true and false (without quotes) are reserved for boolean values in Lua.

Two-dimensional table

The previous example can be extended by adding a table with a table:

function ptab ( tabx )
    if type ( tabx ) == "table" then
        for ii = 1, #tabx do
            if type ( tabx [ ii ] ) ~= "table" then
                print ( "entry " 
                            .. ii .. 
                        ", type = " 
                            .. type ( tabx [ ii ] ) .. 
                        ", value = "
                            .. tostring ( tabx [ ii ] )
                      )
            else
                print ( "entry " .. ii .. " is a table" )
                ptab ( tabx [ ii ] ) 
                print ( "end of entry " .. ii )
            end
        end
    end
end

local tab1 = { "a", 2, "beta", 4, false, { 1.12, false, "bad", },  }

ptab ( tab1 )

This example is more complex. The loop and the print statement have been moved inside a function ptab. For most entries, ptab just prints the entry as before. If the entry is a table, ptab calls itself, passing the entry to print the table. This is an example of a recursive function, and demonstrates a case where it's a good idea.

The table is same as the one in the previous example, but now contains another table as it sixth entry.

The example produces the following output:

entry 1, type = string, value = a
entry 2, type = number, value = 2
entry 3, type = string, value = beta
entry 4, type = number, value = 4
entry 5, type = boolean, value = false
entry 6 is a table
entry 1, type = number, value = 1.12
entry 2, type = boolean, value = false
entry 3, type = string, value = bad
end of entry 6

Dynamic table

In Foldit, it's common to scan all the segments of a protein to obtain information. Saving the results in a table is easy.

local segCnt = structure.GetCount () 

print ( "segment count = " .. segCnt ) 

local tabx = {}

for ii = 1, segCnt do 
    local aa = structure.GetAminoAcid ( ii )
    local ss = structure.GetSecondaryStructure ( ii )

    tabx [ #tabx + 1 ] = { ii, aa, ss, }
end

for ii = 1, #tabx do 
    print ( "segment " .. tabx [ ii ] [ 1 ] .. ", amino acid = \"" .. tabx [ ii ] [ 2 ] .. "\"" )
end

This example first uses structure.GetCount to get the number of segments in the protein. For each segment, it calls structure.GetAminoAcid and structure.GetSecondaryStructure to get the amino acid and secondary structure codes for the segment.

The example stores the segment number, the amino acid code, and secondary structure code in the table tabx, which starts out empty.

The statement tabx [ #tabx + 1 ] is a standard way for adding new indexes to a table. Since tabx starts out empty, the statement tabx [ ii ] would also work on the left-hand side, but #tabx + 1 is more general-purpose.

On the right-hand side, the statement { ii, aa, ss, } simply defines a new table, using the same syntax used for a static table in the previous examples. The only difference here is that variables are used for the table entries. The values of the vvaribles are retrieved and placed in the table when this statement is executed. The values in the table don't change if the values of the variables change later.

Inside the first for loop, the variables aa and ss are marked as local. This means they go out of scope at the bottom of the loop. Technically, this means that aa and ss are new each time through the loop.

The second for loop in the example prints the amino acid code for each segment. It processes tabx as a two-dimensional table, so there are two sets of square braces [ and ] used to get the values. The first set addresses the "row" of tabx, containing the segment information. The second set of braces addresses the "column", containing the specific items of information about each segment. We're just using 1, 2, and potentially 3 to address the columns, so we need to be remember what's in each column.

The output looks something like this:

segment count = 119
segment 1, amino acid = "n"
segment 2, amino acid = "e"
segment 3, amino acid = "k"
segment 4, amino acid = "i"
segment 5, amino acid = "l"
segment 6, amino acid = "i"
segment 7, amino acid = "v"
...

As in the previous example, tabx is a table of tables. For a larger table, it might be help to define varibles to be used as column indexes:

SEGINDX = 1
SEGAA = 2
SEGSS = 3

This way, the names SEGINDX, SEGAA and SEGSS can be used to retrieve the contents of tabx instead of the numbers. This is clearer, and it makes it easier to change the table's format later on.