—
I've rewritten the Lua Tricks tutorial and hosted it on my github page. You can access it here: http://kingdaro.gith...lua-tricks.html
Old content for reference:
Spoiler
Lua Tricks
Note that this is not a lua tutorial and is intended for intermediate to advanced lua users.
I've been looking at some peoples' programs around here and I notice that a lot of them aren't using some neat tricks that may or may not make coding easier/neater.
Strings
The first thing is that a lot of people use double quotes instead of single quotes, but I think that's just a preference in most cases. In case you still aren't aware:string1 = '' string2 = ""
Both of these string variables are valid. I should probably mention that you should be careful with using single quotes, because there are common words with apostrophes such as "don't" and "you're". To prevent errors, you should put backslashes before single quotes inside single quotes:string1 = 'don\'t'
An alternate solution is to use double quotes in this case.
There's also this neat trick that I didn't learn until a little further down the line in my use of lua - multiline strings. Defined by [[ ]], much like ' ' and " ".
The following example is valid:print [[This is a multiline string.]]
Be wary that using multiline strings with tabs before text will actually output the tabs as well.print [[This string will take the tabbing with it.]]
Multiline strings are useful in printing menus for your console. Instead of doing this:print '------------------------------' print 'Screen Title' print '------------------------------' print 'Option' print 'Option' print 'Option'
you can do:print [[ ------------------------------ Screen Title ------------------------------ Option Option Option ]]
A similar helpful trick to multiline strings can be done with comments, starting with –[[ and ending with ]]:--[[ This is a multiline comment. It does nothing :P/>/>/>/> ]]
I learned this very recently from an amazing youtuber named NitroFingers. You can use the # operator on strings to get the length of a string, much like string.len().myString = 'waffles' print(string.len(myString)) --> 7 print(#myString) --> 7
I haven't tested this with all string methods, but in most cases, you'll usually be able to perform the method on the actual string instead of calling the method with the string as an argument.local s = 'waffles' s:sub(1,3) --> waf s:match 'f.-s' --> fles for i,v in s:gmatch '.' do print(v) end --> w --> a --> f --> f --> l --> e --> s
Functions
Function calls are also flexible in other ways. In the case that a function only takes one argument and it is a string, you don't have to supply parentheses.Both of these examples will print a string.print('a string') print 'a string'
Unless you're getting deep into some OOP stuff, this won't be very helpful to you in a lot of cases. If a function takes a table argument, you can drop the parentheses, like strings.myFunction { a = 1; b = 2; }
There's also an alternate way of defining functions.myFunct = function() -- stuff end
For me, this is useful for defining functions without having to go outside your table:myTable = { funct1 = function() print 'stuff' end; funct2 = function() print 'more stuff' end; } myTable.funct1() myTable.funct2()
Functions are able to be defined with a variable number of arguments:function stuff(...) end
The three dots represent the table of arguments given and can me accessed in the function as "arg".function stuff(...) for i,v in pairs(arg) do write(v) end end stuff(1,2,3,'a','b','c') --> 123abc6
Woah wait, where'd that 6 come from? That's just the number of arguments given. It's indexed in the "arg" table as n.print(arg.n) --> 6
However you'll still get the number of arguments given if you use the pound sign operator, as it only counts values given as indexes.print(#arg) --> 6
At the end of the day, it's all a matter of preference. To make things less complicated, you can simply store your arguments in a local table, as shown by some computercraft base programs.tArgs = { ... }
If you don't want to check for individual arguments in a table, you can catch variable arguments using variables instead.Thinking about it is kind of screwy, but makes sense at the same time.-- the old way: local args = {...} local length, height = args[1], args[2] -- the new way local length, height = ...
In lua, you have the ability to directly call anonymous functions without having actually defined them yet. It will sound less confusing when I use an example. Say I wanted to have a table full of all of the available colors in numerical order.local colorTable = (function() local t = {} for _, color in pairs(colors) do if type(color) == 'number' then table.insert(t, color) end end table.sort(t) return t end)() print(colorTable[1]) --> 8192
Functions can be used as "iterators", which is a fancy term for a function that keeps returning, and at some point, returns nil, such as when a table is empty, when there are no more lines in a file, or when a number has reached a certain limit. Iterators are used in "for" statements, like with pairs or ipairs.-- this function returns random numbers from 1 to 10, but doesn't return anything if the number is 10. local function randomNumbers() local n = math.random(1,10) if n == 10 then return nil end return n end -- this is how we'd do it the old way. while true do local n = randomNumbers() if n then print(n) else break end end -- and this is using it as an iterator for n in randomNumbers do print(n) end
A more applicable example:local file = fs.open('my-data', 'r') local lines = {} -- the old way: while true do local line = file.readLine() if line then table.insert(lines, line) else file.close() break end end -- the new way: for line in file.readLine do table.insert(lines, line) end
I feel like you may be wondering something, though. In the examples above, our iterators don't have parentheses. However pairs() and ipairs() do. This is because the functions pairs and ipairs aren't iterators, but they return iterators. They return the next() function, which returns the next key/value pair in a table after the one given, like how file.readLine returns the next line in a file. The next() function, however, does not automatically know which index you're currently on. It must be stored and given to it he next time you want a key/value pair out of it.local data = { name = 'Kingdaro'; title = 'The Doctor'; posts = 'A lot'; } local key, value -- we do not have a current "position" in the table, so we just get the first index that lua can see. key, value = next(data) print(key, value) --> posts, A lot -- note that lua will not always find the indices in order. -- so we take the current key we have, and use it to find the next index and key. key, value = next(data, key) print(key, value) --> name, Kingdaro -- and again key, value = next(data, key) print(key, value) --> title, The Doctor -- at this point, we run out of key/value pairs, so we just get nil after we try to get another. key, value = next(data, key) print(key, value) --> nil
And what we did above is what the function that pairs() returns does. Remember that it's not pairs() itself, because if it were, we couldn't pass a table to iterate through using next().
Tables
Like strings, the # operator can also be used to find the length of tables.myTable = {'hello', 'world!', 3, 4, 5} print(#myTable) --> 5
This is tricky though; the length of a table is counted up until the last non-nil index. Setting an index in the middle of a table to nil will still have its elements counted up until the last value that isn't nil. If the last value becomes nil, the counter decreases until it's found a non-nil value. For example, the length of {1, nil, 3} is three, and the length of {1, 2, nil} is two. Indices that are strings do not count, so the length of {5, 7, 3, hello = 'world'} would be three.
Moving on, when I see people do this:I die a little on the inside.something = {} something['key'] = 'value' something['sub'] = {} something['sub']['key'] = 'value'
It's not very clean, and it's much more typing than doing this:Defining tables and subtables this way allows you to more accurately see the hierarchy of the table, and as stated before, is much cleaner.something = { key = 'value', sub = { key = 'value' } }
Note that there's still an alternative even when you need to define new indices after making the table. You can use a dot syntax instead of the string around brackets.local stuff = {} function defineStuff() stuff.x = 5 stuff.y = 5 stuff.fun = { foo = 'bar'; } end defineStuff() print(stuff.fun.foo) --> bar
However, brackets are required when using string variables, and the dot syntax is only used to get and set raw indices.local stuff = {} local key = 'hello' stuff[key] = 'world' -- this is wrong; we actually get the key named "key" instead of the value of the variable key print(stuff.key) --> nil -- this is right; the value of the variable key is "hello", and up there, we set the key named "hello", the value of the key variable. print(stuff.hello) --> world
Also, when defining indices in tables, you can actually use semicolons instead of commas. For me, this is easier because I can rearrange elements of the table without having to redo my commas.myTable = { index1 = "foobar"; index2 = 1337; }
In case you didn't know this either, you can also use strings to access indexes not defined by strings. This would be helpful for storing functions for possible user input in tables.local answers = { yes = 'You\'re cool!'; no = 'Fine, be that way.'; } print 'Do you like pineapples?' input = read() -- this is accepted as a string. if answers[input] ~= nil then print(answers[input]) else print 'I don't understand you, sorry.' end
Tables and multiple arguments/variables get along really well. Remember you can do this:args = {...}
In this situation, … could represent "a, b", or "foo, bar, baz", or just "hello". Therefore, you can do this:local function add(n1, n2) print(n1 + n2) end local myTable = {5, 15} add(unpack(myTable))
And you can do this:local function numbers() return math.random(1,10), math.random(11, 20) end local myTable = {numbers()) print(myTable[1]) --> a random number from 1 to 10 print(myTable[2]) --> a random number from 11 to 20
In a situation applicable to ComputerCraft, you can even do this:while true do local events = {os.pullEvent()} if events[1] ~= 'mouse_click' then doEvents(unpack(events)) end end
This catches the events, and only sends them to the doEvents function if the event isn't mouse_click.
Numbers
There isn't much to learn in this category, however I think you'll eventually find it useful that you're actually able to concatenate numbers like strings.There need to be spaces in between the dots and the numbers, otherwise lua will confuse your dots as decimals and throw an error.print(3 .. 3) --> 33
Some of you coming from a different programming background may have already known this, but the function math.fmod() can be replaced with the operator %. If you don't know what this does, it returns the remainder of the division of two numbers. e.g. 7 % 3 would equal 1.
You could use this to tell if a number is even - if the remainder of the number and 2 is 0.while true do textutils.slowPrint 'Type in a number.' write '> ' local input = read() local num = tonumber(input) if num then --checking if the input was successfully converted into a number if num % 2 == 0 then textutils.slowPrint 'Your number is even.' else textutils.slowPrint 'Your number is odd.' end elseif input == 'exit' then --give the ability to leave the program. textutils.slowPrint 'Thank you for using my useless program! :P/>/>/>/>/>/>/>/>/>/>' sleep(1) break else textutils.slowPrint "That's not a valid number!" end sleep(1) end
Booleans
In the bools field, there's actually a cute little statement that is way easier than if … elseif … end:Five unnecessary lines condensed into one. Here's an example:var = condition and set_if_true or set_if_false
In this case, var would be 'yes!' because 5 > 3. If on some distant alien planet in a parallel universe 5 were less than three, var would be 'aw'.var = 5 > 3 and 'yes!' or 'aw'
If you wanted to have a bit of fun, you can make yourself a clamp function by nesting these.local function clamp(low,n,high) return n < low and low or (n > high and high or n) end
There's also a simple way of reversing a boolean:bool = not bool
In a more applicable example, you could make a sort of toggling light switch program with one line (assuming your output is in the back and hooked up to your lights)rs.setOutput('back', not rs.getOutput('back'))
Oh, and remember that % example? This:if num % 2 == 0 then textutils.slowPrint 'Your number is even.' else textutils.slowPrint 'Your number is odd.' end
Could be this:Brackets needed because it isn't just one string anymore XDtextutils.slowPrint('Your number is ' .. (num % 2 == 0 and 'even.' or 'odd.'))
Metatables
Christ, where do I begin.local myTable = { value = 5; anotherValue = 10; } local mt = { __call = function(tab, index, value) if not index then return end if value then tab[index] = value end return tab[index] end; } setmetatable(myTable, mt)
What I just did here, is that I just made a callable table. Basically, I've made it so that this won't error:myTable()
Furthermore, in my specific example, I can get and set the keys and values of myTable through function calls.print(myTable('value')) --> 5 myTable('value', 10) print(myTable('value')) --> 10
This is but one of the very many powerful functions of metatables. A metatable is like a child or sibling to a normal table. When you do something with a metatable, such as call it like a function, lua looks for a certain value in the table's metatable so it knows what to do in that situation. These values are called "metamethods". A very simple and commonly used metamethod is __index.
The __index metamethod is triggered when you try to access a value in a table that doesn't exist. If you define the __index metamethod as a table, Lua will try to look for a key in that table that doesn't exist in the original table. With this system, it's easy to make a lookup table:local lookupTable = { someValue = 5; } local myTable = { -- no values here! } print(myTable.someValue) --> nil setmetatable(myTable, { __index = lookupTable }) print(myTable.someValue) --> 5
There's also __add, __sub, __mul, __div, and __tostring among others. You can find more about metatables on lua-users. http://lua-users.org...methodsTutorial
You could've lived your life without knowing any of this, really. However I thought it'd be helpful to some of you people, a couple of interesting tidbits to know. Pretty sure I'm missing some stuff but I'm not necessarily a book of infinite useless lua knowledge :D/>/>/>