This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Mads's profile picture

[Advanced][Snippets]Metatables & Metamethods

Started by Mads, 30 March 2012 - 07:02 AM
Mads #1
Posted 30 March 2012 - 09:02 AM
Hello, mad1231999 here, and I wan't to speak out about whet I learnt. I think many people have been wondering: Why does some functions require a colon? And this is one of the things that I'll try to teach you guys! This tutorial will not have anything to do with turtles, or anything like that, so if you wanna learn about turtles(CC stuff in general), this is not the tutorial for you.

Tables are very useful, but they all behave the same way(mostly). This is where metatables are very good.
A metatable is a very powerfool tool, if used correctly. They allow you to change the immediate behaviour of tables. E.g. ig we have a table inside a table, like this:

local t = {}
t._t = {str = "Hello World!"}
We could set the default "address" to the @_t table, so if we did this:

print(t.str)
Even though the @str variable is in @t._t, and we are calling it through t@, it would still print "Hello World!".
To do this, we need write this code before the print statement:

local mt = {__index = t._t}
setmetatable(t, mt)
Now, when we run our program, it should print "Hello World!". Link to full program.


That is probably the easiest thing to do with metatables, but it is still useful.
I will now show you some more advanced usages of metatables, and metamethods too.
I have written a program, which stores a person's data upon call:

local getPerson = {}
local mt = {
__call = function(table, _name, _age, _gender) --Creating the __call function, which gets called everytime we call the table
local person = {name = _name, age = _age, gender = _gender} --Passing the parameters into the person
return person
end
}
setmetatable(getPerson, mt)
local john = getPerson("John", 27, "male") --The __call function gets triggered
print(john, "njohn's name: "..john.name.."njohn's age: "..john.age.."njohn's gender: "..john.gender.."n") --We get a table with the values name, age and gender

Output of this program should look like this:

table: --Random numbers and letters
john's name: John
john's age: 27
john's gender: male


One of the best things about metatables, is that we can use Object Orianted Programming in Lua! This is a bit more advanced, so hang on.

First an example of OOP in Lua:
io.open() return a "file", which is actually a table. When calling a function in that file, we use @file:<function>(…). If this was not OPP, we would do it like this: <function>(@file, …).

In my example I will create a game character, with some properties. If these properties are not specified upon call, they will default to the standard state:

local Man = {}
Man.default = {name = "Noname", strenght = 100, speed = 67} --Creating a table with the default properties
Man.mt = {__index = Man.default} --Creating the metatables, which will index to Man.default
function Man:new(t)
	setmetatable(t, Man.mt)
	return t
end


That was simple enough, now for making a character:

local man = Man:new({speed = 5}) --Creating an instance of Man, called man
print("Man's speed: "..man.speed) --Print all the properties of @man
print("Man's strenght: "..man.strenght)
print("Man's name: "..man.name)
Link to full program

The output of this program should look like this:

Man's speed: 5
Man's strenght: 100
Man's name: Noname


In more advanced OOP, you will normally be able to change values on the fly, and that is what I'm gonna show you how to do now.
In this example, I will create a "prototype" called Dog, and put some functions inside it

--Most of this should be familiar
local Dog = {}
Dog.__index = Dog

function Dog.new(_name, _sound)
local dog = {}
setmetatable(dog, Dog)
dog.name = _name
dog.sound = _sound
return dog
end

function Dog:speak() --This is where it gets abit tricky
print(self.name..": "..self.sound) --When using @self, we are reffering to the instance used in Dog's place upon call
end

function Dog:setName(_name)
self.name = _name We are setting the name of the instance used to the parameter @_name
end

function Dog:setSound(_sound)
self.sound = _sound
end

local dog = Dog.new("DoggyBoy", "Bark") --Creating an instance of Dog called dog
dog:speak() --Calling dog:speak() and changing properties
dog:setSound("Hello World!")
dog:speak()
dog:setName("CoolGuy")
dog:speak()

The output of this program should look like this:

DoggyBoy: Bark
DoggyBoy: Hello World!
CoolGuy: Hello World!


Now for some more advanced, and very useful ways of storing data. We will be using the 2nd program from this tutorial.

local getPerson = {}
local mt = {
__call = function(table, _name, _age, _gender)
local person = {name = _name, age = _age, gender = _gender}
return person
end
}
setmetatable(getPerson, mt)
local john = getPerson("John", 27, "male")
print(john, "njohn's name: "..john.name.."njohn's age: "..john.age.."njohn's gender: "..john.gender.."n")

If we insert this on line 2:

getPerson.cache = {}
We will now have a cache for our table. Now, how can we use this? I'll tell you. But first: Why is this good?
It is good, because if we try creating a new guy, with a name that already exists, then why create a new guy and use more memory, when we can just use th old one?

In order to achive this, we must change a bit in the code. The code should look like this:

local getPerson = {}
getPerson.cache = {}
local mt = {
__call = function(table, _name, _age, _gender)
if table.cache[_name] then return table.cache[_name] end
local person = {name = _name, age = _age, gender = _gender}
table.cache[_name] = person
return person
end
}
setmetatable(getPerson, mt)
local john = getPerson("John", 27, "male")
print(john, "njohn's name: "..john.name.."njohn's age: "..john.age.."njohn's gender: "..john.gender.."n")

local _john = getPerson("John")
print(_john, "n_john's name: ".._john.name.."n_john's age: ".._john.age.."n_john's gender: ".._john.gender.."n")
Now, when we run the program the output should be like this:

table: --Random numbers and letters
john's name: John
john's age: 27
john's gender: male

table: --SAME random numbers and letters as before
_john's name: John
_john's age = 27
_john's gender = male

What we changed, is that whenever we try creating a new person, we check if that person already exists. If it does, we return the person that exists. If not, we create the person, and store it in the cache.


Thanks for reading this entire tutorial, I will add more along the way, as I learn more about metatables!
I really hope you liked it, and learnt something from it.
Please leave a comment, saying what you think, and if you think I should improve something.
Thanks
-mad1231999
Advert #2
Posted 30 March 2012 - 01:08 PM
Not bad; here are a few thoughts:

You don't need to explain what every line does, it just forces the reader(s) to read unnecessary stuff that they should already know.

It'd also be easier to read the commends if they were actual comments, in the code; then we won't have to scroll up and down all the time, and we won't need to know what line number it is.

Also, Lua does not have 'classes' built in, so it's not really a good idea to compare this to them; Lua uses what is known as prototyping: http://en.wikipedia.org/wiki/Prototype-based_programming

I also think that some more practical (in CC) applications would help people understand the examples, instead of using the standard person/dog examples.
Mads #3
Posted 30 March 2012 - 01:18 PM
Not bad; here are a few thoughts:

You don't need to explain what every line does, it just forces the reader(s) to read unnecessary stuff that they should already know.

It'd also be easier to read the commends if they were actual comments, in the code; then we won't have to scroll up and down all the time, and we won't need to know what line number it is.

Also, Lua does not have 'classes' built in, so it's not really a good idea to compare this to them; Lua uses what is known as prototyping: http://en.wikipedia....sed_programming

I also think that some more practical (in CC) applications would help people understand the examples, instead of using the standard person/dog examples.
Thanks for the advise. Some practical program would be easy to add in. And the comment, I can just copy some of the informations into the code.