Collatz
logo.gif
Let us return for the moment to the program collatz .

local n = 0 -- count steps
local mutual = {

    [0] = \ (self, x) print (x); n += 1;
            => self:collatz (x//2) end,

    [1] = \ (self, x) print (x);  n += 1;
             => self:collatz (3*x + 1) end,

    collatz = \ (self, x)
                  if x < 2 then => n end -- if
                  =>  self[ x%2 ] (self, x)
                  end,
         }
print "Enter a positive whole number"
repeat
    x = io.read  "*n"
    if x < 1 then print "Try again" end
until x > 0
print "-----------"
local m = mutual:collatz (x)
print "-----------"
print (m, " steps taken")
In the first line we define a variable n and initialise it to zero. The symbol -- indicates that the rest of the line is a comment. The word local indicates that n is local to the current block, but as this is the whole program we could have omitted it. It is here because using local variables is a good habit.

In the second line we define the variable mutual as a table, as indicated by the curly brackets. The table has three items, separated by commas (you can use semicolons if you prefer). In each, on the left of the = is the item's key or label enclosed in square brackets, and on the right is the item's value. In this case the values are all functions, given by the expressions starting with \ and ending with end . The keys are the numbers 0 and 1 and the string "collatz". Note that in Lua [ "collatz" ] can be written more simply as collatz. This removal of the quotes is only possible when the string satisfies the rules for variable-names.

The user is repeatedly asked to enter a number x until it is positive. The string "*n" tells the system to read a number from the standard input. Note that if statements are also terminated by the word end .

The expression mutual:collatz (x) can be unwound, first as mutual.collatz (mutual, x) (by definition of the colon symbol), and then mutual.collatz as mutual[ "collatz" ] (by definition of the dot symbol). So we see that the first argument, self , of the three functions will be replaced by the table itself. This is a common, and a useful, trope in Lua. Putting functions into a table like this is the standard way of implementing mutual recursion. We have separate functions for the even ( [ 0 ] ) and odd ( [ 1 ] ) cases, and the third for pulling them together.

Tables are Lua's sole datatype. They combine what in other programming languages are called arrays, lists, dictionaries, environments and objects . One may talk of a function f evaluated at an argument x to produce a value f ( x ), and of a table t indexed at a key k to produce a value t [ k ]. The opposition of round brackets to square indicates the contradiction between time and space. With a function you know how much memory it takes up, but you do not know how long evaluation will take. With a table you know how long look-up will take, but not how much memory the table takes up. In Lua you have the choice: do I use square or round brackets? In this example the square brackets in

         self[ x%2 ] (self, x)
show that the branching of the thread of execution according to whether x is even or odd is done by lookup, not by calling a function, and that is more efficient.

Just as arithmetic does not work well without a zero, so types in programming languages do not work well without nil, which is the sole value (or rather un-value) of the type nil .

   print (type (nil))         --> nil
It is the value that a variable has by default if it is undefined. To remove a key from a table, for example, set its value to nil.

   t[k] = nil     -- k no longer a key of t 
A function that has no return statement is deemed to return nil. This may seem metaphysical, but it is useful.

The type boolean has two values: true, false. Values of all other types are coerced to a boolean when they occur after if , while , until , or before and , or . The un-value, nil, is coerced to false, all other values to true (including zero and the empty string). The logical operators (and, or) are lazy in their right-hand operands. So

 print ( 2 == 3 and 1/0 )         ---> false
 print ( 5 == 4 + 1 or 1/0 )      ---> true
because the expression 1/0 never gets to be evaluated.

 print (not nil)          ---> true
 print (not not nil)      ---> false
 print (not not 0)        ---> true
So double negation gives the coercion to boolean explicitly. The switch expression

           x and y or z
evaluates to y if x is neither false nor nil, and to z otherwise.

Functions can return more than one argument at a time.

  divmod = \ (x, y) => x//y, x%y end
  print (divmod (23, 7))    ---> 3   2
Note that multiple assignments are not necessarily the same as consecutive single assignments, because each assignment alters the environment. Thus

  x, y = y, x
simply interchanges the values of x and y.

The parenthesized list of formal parameters of a function indicate how a function expects to find its arguments on the (argument-)stack. It removes them from the stack and stores them into local variables. The function's return statement puts values back on the stack.