Skip to content

stevedonovan/lua-gentle-intro

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 

Repository files navigation

Values and Expressions

Lua can be started from the command-line and used as a calculator:

$> lua53
Lua 5.3.0  Copyright (C) 1994-2014 Lua.org, PUC-Rio
> 2*5 + 3
13
> 2*(5 + 2)
14
> 5^2 + 1
26.0

Regular numbers are the simplest kind of value, and can be combined into expressions using operators. +,* and ^ (meaning 'to the power of') are common arithmetic operators.

It would not be a very good scientific calculator if the standard mathematical functions were not available:

> math.sin(0.4) + 1
1.3894183423087

This function takes a number value, and calculates the sine of that number in radians. The conversion from degrees is straightforward, so we will calculate it and give that value a name:

> d2r = math.pi/180
> math.sin(90*d2r)
1.0

d2r is called a variable, and giving it a value is called assignment. You should not pronounce = "equals" but rather call it "becomes" or something like that; it does not mean checking for equality! This is where standard programming notation is different from mathematics. Otherwise this would make no sense:

> x = 1
> x = x + 1
> x
2

Functions are a kind of value as well, so you can define shortcuts:

> s = math.sin
> c = math.cos
> s(x)^2 + c(x)^2
1.0

Comparisons between values involve another kind of expression involving conditional operators. Note == here does mean 'test for equality':

> 10 > 12
false
> 42 == 42
true
> 5 < 7
true

These are often called 'boolean' operators after the English logician George Boole, and the resulting value is called 'boolean' in Lua.

Tables

Arrays are an important kind of value in programming languages. They store a sequence of values. They have a size which is returned by the # operator, and can be indexed using a integer value:

> arr = {10,20,30}
> #arr
3
> arr[1]
10
> arr[3]
30
> arr[20]
nil

Note that Lua arrays go from 1 to the length of the array. It is not an error to index outside that range - you will simply get nil. This is a special value meaning 'no value'. This is also the value of an undefined variable:

> frodo
nil

Maps (often called associative arrays or lookup tables) are also indexed but by arbitrary values. They consist of key/value pairs.

> map = {one=1,two=2,three=3}
> map['one']
1
> map.one
1
> map.four
nil
> map.four = 4
> map.four
4

Note an important feature of Lua maps - you can use [] as before (except with strings in this case) but map.key is exactly the same as map['key']. And if the key is not present in the map, then the result is (again) nil.

In Lua, both maps and arrays are called tables. We used math.sin to calculate the sine of a number, and math is just a table of functions. Generally you would use a table either as an array or a map, but they can be mixed.

Tables are a distinct type, which is the proper term for 'kind of value'. It is an error to pass the wrong type to a function expecting a number:

> math.sin(map)
stdin:1: bad argument #1 to 'sin' (number expected, got table)
stack traceback:
	[C]: in function 'math.sin'
	stdin:1: in main chunk
	[C]: in ?

Strings

Strings are quoted text like "hello". You can quote with either double or single quotes, Lua does not care. The length operator # will give the size in bytes:

> s = 'hello'
> #s
5

By byte I mean a 8-bit value (the smallest addressable unit of memory); for ASCII text, a byte is a character, but in general a character may be several bytes. Unlike tables, you cannot index a string. You cannot modify a string either, it is said that they are immutable.

Strings are a distinct type of value, and so you get a type error when using them in the wrong way. But if they represent a number, then tonumber will convert that string into a number.

> math.sin(s)
stdin:1: bad argument #1 to 'sin' (number expected, got string)
...
> s = "42"
> tonumber(s)
42

Like real-world strings, they can be combined or concatenated using ... Or you can join a table of strings and numbers together using table.concat:

> hello = "hello"
> world = "world"
> hello.." "..world
hello world
>  table.concat({"hello","world",42},' ')
hello world 42

You can extract a part of a string using string.sub:

> string.sub(hello,1,3)
hel

The original string is not changed in any way, but a 'slice' is copied to a new string.

What is Programming?

Up to now, Lua is a convenient calculator. The concepts of value, expression, variable and assignment are important programming concepts, but we haven't started programming yet!

  • defining our own functions
  • doing different things based on some condition
  • repeating things until some condition is true

Now it's time to introduce statements. An expression always has a value; an assignment like x = 2 is a statement and has no value.

To conditionally perform some action, use the if statement:

> x = 2
> if x > 1 then print("ok") end
ok

These statements are generally if EXPRESSION then STATEMENTS end. The words if,then and end are keywords, and these are reserved words in Lua. You cannot use keywords for variables.

The if statement can also have an else clause:

> if x > 1 then print("ok") else print("nope") end

To repeat things with a range of values, use the for statement:

> for i = 1,5 do print(i) end
1
2
3
4
5

At this point, it's time to start writing programs. Type this into a text editor:

-- a comment, ignored by Lua. This is 'hello.lua'
for i = 1,5 do
   print('hello',i)
end

and then

$> lua53 hello.lua
hello	1
hello	2
hello	3
hello	4
hello	5

It's important to format code nicely, because it makes it easier to read. Anything between the do and the end should be indented. Tabs, spaces, it doesn't matter: just be consistent. Use a text editor that understands this (I'm personally a fan of Geany).

Can now add an if statement:

-- goodbye.lua
for i = 1,5 do
   if i > 2 then
      print('goodbye',i)
   else
      print('hello',i)
   end
end

which gives us:

hello	1
hello	2
goodbye	3
goodbye	4
goodbye	5

Again, indent each statement properly. You could put this all on one line but people will hate you.

Every Lua program has a predefined table called 'arg' containing the command-line arguments passed to the program:

$> cat args.lua
-- args.lua
for i = 1,#arg do
   print(i,arg[i])
end
$> lua53 args.lua one 42 'hello dolly'
1	one
2	42
3	hello dolly

You can use tonumber to convert the first argument to a number. We will use io.write which works like print except it doesn't end the line:

$> cat for.lua
-- for.lua
n = tonumber(arg[1])
for i = 1,n do
     io.write('hi ')
end
print()
$> lua53 for.lua 5
hi hi hi hi hi

There's another function in the io table which allows us to easily loop over all the lines in a file. We assign 1 to i, and then increment i by one each time.

-- lines.lua
file = arg[1]
i = 1
for line in io.lines(file) do
   print(i,line)
   i = i + 1
end

And the output:

$> lua53 lines.lua lines.lua
1	-- lines.lua
2	file = arg[1]
3	i = 1
4	for line in io.lines(file) do
5	   print(i,line)
6	   i = i + 1
7	end

This is other form of the for statement, which works with iterators. Here's another iterator function pairs. It gives all the key/value pairs in a table - in this case the predefined table math:

> for k,v in pairs(math) do print(k,v) end
min	function: 0x41e1d0
tan	function: 0x41dfb0
modf	function: 0x41e5d0
maxinteger	9223372036854775807
asin	function: 0x41e4a0
ceil	function: 0x41e580
rad	function: 0x41df50
random	function: 0x41e080
mininteger	-9223372036854775808
floor	function: 0x41e690
huge	inf
max	function: 0x41e260
sqrt	function: 0x41dfe0
pi	3.1415926535898
tointeger	function: 0x41e6e0
atan	function: 0x41e440
abs	function: 0x41e760
sin	function: 0x41e020
acos	function: 0x41e4d0
randomseed	function: 0x41e050
log	function: 0x41e2f0
ult	function: 0x41e3a0
cos	function: 0x41e410
fmod	function: 0x41e7d0
type	function: 0x41e500
deg	function: 0x41df80
exp	function: 0x41e3e0

(For more information, go to Mathematical Functions in the Lua manual.)

Defining Functions

In mathematics, simple functions take a value from a set of all possible inputs, the domain and output a value from a set called its range. Or, we apply a function to any value from the domain and get a value from the range. So the simplest form of 'sine' goes from all real numbers to the range (-1,+1).

In programming we call a function and pass it an argument. It will then return a value. These words may seem strange (they are not how we talk about mathematical functions) and come from the early days of programming, when 'calling' a 'subprogram' involved saving your position in memory and jumping to a new position. The subprogram would do its work, and then would 'return' to the saved original position.

The keyord function defines a function in Lua; it is followed by the name, and then the names of arguments. Like for and if, any number of statements may appear afterwards ending with end. The return statement passes the value back explicitly:

-- sqr.lua
function sqr(x)
    print('x is',x)
    return x * x
end

y = sqr(10)
print('y is',y)
print('x is',x)
-- output
-- x is 10
-- y is 100
-- x is nil

The value 10 is assigned to the variable x in the function sqr, and we get back 100.

Note that x is nil outside the function! That's to say, it's undefined.

Consider these lines in the Lua Prompt:

> sum = 0
> for i = 1,50 do sum = sum + i end
> sum
1275
> = i
nil

Again, i is only defined inside the loop, between the do and end.

These are examples of local variables, which are only visible in a particular part of a program; global variables are visible everywhere. Life without local variables would be a mess, honestly. Consider this more readable version:

function sum_numbers (n)
    sum = 0
    for i = 1,n do
        sum = sum + i
    end
    return sum
end

It does the job, sure, but every time we call it, it updates a global variable called sum. Now there are only so many meaningful names you can give to variables (without stupid_long_names_creating_confusion) and you do not want the insides of functions 'escaping' like this! Setting the global variable sum is a side effect and is something to be avoided.

So this is better:

function sum_numbers (n)
    local sum = 0
    for i = 1,n do
        sum = sum + i
    end
    return sum
end

The keyword local declares a variable, and usually also gives it an initial value. sum is now local to sum_numbers (like the argument n) and we have a better behaved function. I encourage you to use local declarations as much as possible, even in little programs.

Lua functions may have more than one argument, like print - it can take any number of arguments. Lua functions may also return more than one value. Say we want the position of some text within a larger body of text - or (properly) the position of a substring in a string:

> s = "hello dolly"
> string.find(s,"doll")
7	10
> string.sub(s,7,10)
doll

string.find returns two values: the start and the end position. You can take these positions and get the substring back using string.sub. The end position is just after the end of the match; all positions are from one. A common task is to find if a string starts with another string, which we can now define as

function starts_with(str,sub)
    return string.find(str,sub) == 0
end

local text = 'hello dolly you're so fine'
local hello = 'hello'

if starts_with(text,hello) then
    print('goodbye')
end

In this comparison, the second return value is ignored.

Here is a function that returns both the minimum and the maximum value of a table of values:

-- minmax.lua
function minmax(values)
   local vmin = math.huge
   local vmax = -math.huge
   for i = 1,#values do
      vmin = math.min(vmin,values[i])
      vmax = math.max(vmax,values[i])
   end
   return vmin, vmax
end

local values = {10,2,5,20,3,12}
local min,max = minmax(values)
print(min, max)
-- 2    20

I've used the constant math.huge, which is guaranteed to be larger than any other number, and also the min and max functions. Please note how we can assign multiple values to several variables at once. This also means that you can say local x, y = 1.0, 2.0.

There are actually two variables called values in this program, one local to the function and the other to the so-called main chunk. They are completely unrelated.

Operations on Tables

There's no built-in way to print out tables in Lua, but it's easy to write a function that will turn an array of strings and numbers into a human-readable string:

-- show.lua
function showa (arr)
    return '{'..table.concat(arr,',')..'}'
end

And then you can load it into the Lua prompt using the 'l' (for 'load') flag. Note that here we don't use the extension '.lua'!

~/lua/luabuild$ lua53 -l show
Lua 5.3.0  Copyright (C) 1994-2014 Lua.org, PUC-Rio
> arr = {10,20,30,40}
> showa(arr)
{10,20,30,40}
>

We can now demonstrate some common operations on tables. We can insert a new value into a table using a position (which starts from one); values can be removed. There is a second form of table.insert which takes two arguments, not three - it will append the value to the end of the table.

> table.insert(arr,1,11)
> showa(arr)
{11,10,20,30,40}
> table.remove(arr,2)
10
> showa(arr)
{11,20,30,40}
> table.insert(arr,50)
> showa(arr)
{11,20,30,40,50}

You can also create an array-like table by setting the values using an index:

> seq = {}
> for i = 1,10 do
>>  seq[i] = i*i
>> end
> showa(seq)
{1,4,9,16,25,36,49,64,81,100}
> #seq
10
> seq[#seq + 1] = 200
> showa(seq)
{1,4,9,16,25,36,49,64,81,100,200}

There is no builtin way to find things in arrays either, but it's easy to write. Just add the following function to show.lua:

function finda (arr, value)
    for i = 1,#arr do
        if arr[i] == value then
            return i
        end
    end
    return nil
end

It's perfectly fine to return early from a function - we've found our position, let's return what we found. If we do not find the value, then at the end of the for loop we return nil explicitly.

We have to restart with lua53 -l show after this change:

> arr = {10,20,30,40,50}
> finda(arr,30)
3
> finda(arr,60)
nil
> if finda(arr,70) then
>>  print('found')
>> else
>>  print('not found')
>> end
not found

Note that we can directly test whether finda returned nil or not. The only two values that are considered 'false' are the boolean false and the value nil. And that's why I returned nil, rather than some out-of-range number like 0.

About

No description or website provided.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published