This is primarily for myself to finally get over the hesitation about Lua and learn useful things about it. One way to do that is to compare and contrast with another languages in the same 'space' and one I'm more familiar with -- Python.
Python and Lua:
NOTE: I'm not going to cover "why not" aspects. If you don't want to use Lua, you will find many reasons, I'm sure ;)
-- Single line comments in Lua start with two dashes: -- -- what I like about the dashes is that I don't have to use shift key -- as I have to in Python to type the # character. -- the print statement is familiar to Python(3) programmers print("Hello, World!") -- you can also use the single quote character for strings -- which I prefer anyway print('Hello, again!') -- multi line strings are bookended by two square brackets message = [[Dear Python Programmer, Lua is a small, fast, and handy language that is often embedded in larger programs, like video games ...]]
-- All n umbers are internally represented as 64 bit double -- of which 52 bits are for numeric representation x = 42 -- this declares a global variable x print(x) -- you can modify it x = 43 print(x) -- Lua has nil for Python's None n = nil -- what if you want to print -- x is 43 -- this would fail: -- print("x is " + x) print('x is :' .. x) -- string concatenation in lua is two dots: .. print('my name is Lua and I mm' .. 2020-1990 .. ' years old') -- the above number is coerced print(tonumber('10')) -- tonumber will convert '10' to a number print(#"hello") -- # will give you length of the string -- we will come to string formatting later
@ Interpreter
se the -e
command line flag to evaluate code in the command line:
% lua -e "print(math.sin(12)) "
-0.536...
The -l
option loads a library
% lua -i -l a -e "x = 10"
The above will enter the interactive mode afer loading the library a
and setting the value of x
to 10
.
The default prompt of Lua is >
. To change it, set the variable _PROMPT
:
% lua -i -e "_PROMPT= lua> "
This will result in a lua>
prompt.
You can preload a lua file BEFORE lua runs its arguments by setting the LUA_INIT
environmental variable.
In a command like: % lua -e "sin=math.sin" lua script a b
, the following args are set in the arg
table:
> for i, v in pairs(arg) do print(i,v) end 1 a 2 b 0 chap1a.lua -4 lua -3 -i -2 -e -1 sin=math.sin
Lua is a dynamically typed language like Python. Much like Python, you can query the type of a variable at run time
usig the type()
function. Example: type("hello")
will print string
.
type("hello") -- string
type(124) -- number
type(nil) -- nil
type(true) -- boolean
type(12.43) -- number
type(type) -- function
type({}) -- table
There are eight basic types in Lua: nil, boolean, number, string, userdata, function, thread and table.
Unlike Python, the boolean values true
and false
are not capitalized.
You can assign functions to variables like in Python, because functions are first class values:
a = print a('hello') hello
userdata
type allows arbitrary C data to be stored in Lua variables.
Expressions denote values. Types of expressions:
{}
Arithmetic operators:
+ - * / ^ % -
Relational operators:
< > <= >= == ~=
~=
is the same as !=
in Python.
Logical operators, much like Python:
and or not
Operator Precedence, from highest to lowest
^
not # - (unary)
* / %
+ -
..
< > <= >= ~= ==
and
or
binary operators are left associative, ^
and ..
are right associative.
Statement types:
Multiple assignment:
a, b = 10, 2*10
x,y = y,x -- swaps x and y
a,b,c = 10, 20 -- c is nil
Local variable's scope is limited to the block where they are declared. Note, in the interactive mode, each line is a chunk by itself and hence the scoping is different. Use the do
.. end
block to create scope.
a = 100
do
local a = 10
a = a + 1
print(a) -- 11
end
print(a) -- 100
Its a good idea to use local
whenever possible. This is one detail where Python and lua are opposite. In Python, a global variable has to be explicitely declard, local is the default, where as in Lua, global is the default and local has to be explicit. I like Python's default.
foo = 100
do
local foo = foo
foo = foo + 1
print(foo) -- 101
end
print(foo) -- 100
num = 42 -- lua does not use block syntax like Python -- you have "then" instead of ":" and the block -- ends with a "end" keyword if num > 40 then print(num) end num = 0 -- lua has elseif vs python's elif if num == 0 then print("num is zero") elseif num < 0 then print("num is negative") else print("number is positive") end -- what's equivalent of python's range? -- Python: -- for i in range(10): -- print(i) for i = 1,10 do print(i) end -- the same can be written in a single line -- since we do not have to follow block syntax for i = 10, 1, -1 do print(i) end -- TODO: for comprehension -- lua also has repeat loop construct i = 12 repeat print(i) i = i - 1 until i < 10 -- There is no equivalent to Python's -= or +=
Functions are defined with function
keyword in Lua vs python's def
function add(x, y) return x + y end print(add(3, 4))
A function has three thigns:
parameters act as local
variables.
If the number of parameters provided to a function call are different than the formal parameters of the function, lua "adjusts" the parameters
function x(a,b) return a + b end
x(9) -- results in error. b is nil
x(9,9) -- 18
x(9,8,7) -- 17. 7 is ignored
You can return multiple values from a function like this:
function swap(x, y)
return y, x
end
a = 1; b = 2
a, b = swap(a,b)
print(a,b) -- 2 1
Again the extra values are discarded:
a = swap(a,b)
print(a) -- 2
Use the table.unpack
function to convert a returned array with index starting from 1. unpack allows you to call any function, with any arguments, dynamically. This is how the generic call mechanism is implemented.
a, b = string.find("helper", "lp")
print(a, b) -- 3 4
string.find(table.unpack({"helper", "lp"}))
Note: before lua 5.3?, unpack was a global function. now its in the table
module.
Use three dots ...
to define a function with variable number of arguments:
function add (...)
local s = 0
for i, v in ipairs{...} do
s = s + v
end
return s
end
print(add(1,2,3)) -- 6
One cool side effect of this is the multi-value id
function:
function id (...) return ... end
This is a way to implement a **kwargs
like functions in Python.
function foo(x, ...)
print(x)
-- do something with the rest ...
-- x is the fixed parameter
end
Use the select
function "selector" to pick a parameter at a position. select(3, ...)
will return the param at the 3rd position.
Lua DOES NOT support named arguments.
-- invalid code
function foo(x="hello", y="world")
-- alternatively..
function foo(arg)
return arg.x .. ',' .. arg.y
end
print(foo{x='hello', y='world'}) -- hello, world
Lua has a special syntax for "object oriented calls" -- the colon operator :
.
obj:foo(x)
and obj.foo(obj, x)
are the same. The :
saves you from typing the obj
as the first parameter to the function foo
. In Python, a method called on object obj
automatically passes the self
as the first parameter. This is where the notations are different between Python and Lua.
You can alternatively declare a function like this: foo = function (x) return x + 3 end
because functions are anonymous.
When a function is written inside another function, it has full access to the local variables of the enclosing funciton; this feature is called lexical scoping.
function sortbygrade(name, grades)
table.sort(names, function (n1, n2)
return grades[n1] > grades[n2]
end)
In the encosing function, the varible grades
is a non-local function, also called upvalues (reminds me of TCL?).
Consider this closure example
function newCounter()
local i = 0
return function ()
i = i + 1
return i
end
end
c1 = newCounter()
print(c1()) -- 1
print(c2()) -- 2
c2 = newCounter()
print(c2()) -- 1
print(c1()) -- 3
Each instantiation of the function keeps its own closure acting over the variable.
Closures can be used to create sandboxes.
TOREAD: tail calls. again
An iterator is any construction that allows you to iterate over the contents of a collection. In Lua iteators are functions. Everytime you call the iterator, it returns the next element from the collection.
Closure provide the mechanism for store the state between succesive calls to the iterator.
function values(t)
local i = 0
return function () i = i + 1; return t[i] end
end
The above iterator returns only the values of a list, unlike ipairs(lst)
.
In the above, values
is a factory, everytime you call this factory, it creates a new closure.
Tables are associative arrays. The index can be any value except nil.
Tables grow dynamically. Tables are objects.
Python has lists, tuples, dictionaries etc., In Lua, you make do with tables.
x = {} -- this is a constructor expression -- tables are anonymous -- below x and y point to the same table x[0] = 99 print(x[0]) y = x y[0] = 100 print(y[0]) print(x[0]) -- Lua's garbage collector will delete the table and reuse its memory when there are no more references to the table. -- you can use the dot notation to access the values in the table, but only if they are string indexes. x['foo'] = 123 print(x.foo) -- this is a table that looks like a python list lst = {1,2,3,9} -- to iterate over it and print it, you use the -- `ipairs` function. become familiar with it, you will be using it a lot. -- ipairs stands for inde pairs? -- ipair gives to you the index, and the value for i, v in ipairs(lst) do print(i, v) end -- a table can also hold heterogenous types stock = {"GOOG", "2020-05-29", 1426.82} for i, v in ipairs(stock) do print (i, v) end -- a table is really a dictionary me = {} -- an empty table me['name'] = 'Pradeep' me['kids'] = 2 -- unlike python, there is no easy way to inspect the contents -- of this dict^H table in the REPL me -- prints -- table: 0x7f84ae601100 -- unlike Python [[ >>> me = {} >>> me['name'] = 'Pradeep' >>> me['kids'] = 2 >>> me {'name': 'Pradeep', 'kids': 2} ]] days = {"Sunday", "Monday"} print(days[1]) -- Sunday -- because indexes start with 1 in Lua -- to override this behaviour days = {[0]="Sunday", "Monday"} print(days[0]) -- shortcut way to init a table a = {x=10, y=20} print(a.x) -- trailing commas do not hurt f = {1,2,} g = {x=10, y=20, "hello", "world"} print(g[1]) -- hello print(g[2]) -- world
Strings are sequence of characters. String are immutable. The escape sequences are familiar:
\a \b \f \n \r \t \v \\ \" \'
-- Python code [[ def get_filename(url): """given https://example.com/foo.html return foo.html """ if url.endswith('.html'): return url.split('/')[-1] ]] -- Lua does not have a big standard library like Python -- so something as simple as the above code becomes -- stackoverflow search :) -- from https://stackoverflow.com/a/20100401 function split(s, delimiter) result = {}; for match in (s..delimiter):gmatch("(.-)"..delimiter) do table.insert(result, match); end return result; end -- I'm going to progressively explore this till I get the -- above Python program in Lua -- The above split function returns a lua Table. split("hello world", " ") -- table: 0x7ffe977008f0
Python comes with a large standard library for everything from dealing with operating system, json, networking, email, http etc
Lua's standad library is not that extensive.
Importing a nonexistant module is a bit more helpful in Lua:
require 'json' stdin:1: module 'json' not found: no field package.preload['json'] no file '/usr/local/share/lua/5.3/json.lua' no file '/usr/local/share/lua/5.3/json/init.lua' no file '/usr/local/lib/lua/5.3/json.lua' no file '/usr/local/lib/lua/5.3/json/init.lua' no file './json.lua' no file './json/init.lua' no file '/usr/local/lib/lua/5.3/json.so' no file '/usr/local/lib/lua/5.3/loadall.so' no file './json.so' stack traceback: [C]: in function 'require' stdin:1: in main chunk [C]: in ?
Compared to Python:
>>> import foo Traceback (most recent call last): File "<stdin>", line 1, in <module> ModuleNotFoundError: No module named 'foo'
What Lua's error message tells us is:
Luarocks is Lua's package manager.
Once you have that installed [^1], you can install the lua-cjson
library. From the name it appears it is a loadable C dynamic library.
$ luarocks install lua-cjson 1 ↵ Installing https://luarocks.org/lua-cjson-2.1.0.6-1.src.rock env MACOSX_DEPLOYMENT_TARGET=10.8 gcc -O2 -fPIC -I/usr/local/opt/lua/include/lua5.3 -c lua_cjson.c -o lua_cjson.o lua_cjson.c:743:19: warning: implicit declaration of function 'lua_objlen' is invalid in C99 [-Wimplicit-function-declaration] len = lua_objlen(l, -1); ^ 1 warning generated. env MACOSX_DEPLOYMENT_TARGET=10.8 gcc -O2 -fPIC -I/usr/local/opt/lua/include/lua5.3 -c strbuf.c -o strbuf.o env MACOSX_DEPLOYMENT_TARGET=10.8 gcc -O2 -fPIC -I/usr/local/opt/lua/include/lua5.3 -c fpconv.c -o fpconv.o env MACOSX_DEPLOYMENT_TARGET=10.8 gcc -bundle -undefined dynamic_lookup -all_load -o cjson.so lua_cjson.o strbuf.o fpconv.o lua-cjson 2.1.0.6-1 is now installed in /usr/local (license: MIT)
We can now use this module
local cjson = require 'cjson' saturn_moons = {'Titan', 'Mimas', 'Dione', 'Phoebe'} text = cjson.encode(saturn_moons) print(text)
Status: Work In Progress
© 2020, Pradeep Gowda
Written in the literate programming style using Literate
Last-updated: Fri, May 29, 2020