Posts Tagged Lua

The Good Stuff of Day 2, Lua Metatables

The “Seven Languages” books (and probably all of the “Seven in Seven Weeks” series) is more a collection of amuse bouche (amuse bouches? amuses bouche? amusi bouchi? nahh) than of entrees or even appetizers. And that makes sense because there’s no way to fit all the details on seven languages in one book. But it also means you read a section and find yourself going “What the heck was that? It was really tasty, but I really want more.”

So it was when I read the section on metatables. Sure there is some info on metatables in Seven More Languages but I really wanted more. I found the Lua book at http://www.lua.org/pil/13.html to be very useful in augmenting it. All of chapter 13 is devoted to metatables and metamethods. I also figured out why the object-oriented stuff got put in Day 2.

So, metatables!

The Amuse Bouche

All tables have metatables, but by default the metatables are empty. You can get to a metatable for a table using getmetatable(sometable) . And, hopefully not surprisingly, you can change a metatable for a table using setmetatable(table, new_metatable) .

So, how do you create a new metatable? And what can go there?

Recall the Florida info table:

stateFL = {
    capital = 'Tallahassee',
    bird = 'mockingbird',
    population = 19890000
}

Trying to print it just gives its address, as is done in many languages.

> print(stateFL)
table: 0050D708

But you can create a function to convert the table to a proper string:

function to_string(t)
    local result = {}
    for k, v in pairs(t) do
        result[#result + 1] = k .. ": " .. v
    end
    return table.concat(result, "\n")
end

Of course, you could just print the table using this new function print(to_string(stateFL)) but that’s not noteworthy. Instead, assign the __tostring function of the metatable. One way to do this is to create a metatable with the function as its only member and make it the metatable of stateFL

mt = {
   __tostring = to_string
}

setmetatable(stateFL, mt)

Then printing the table does the expected thing:

> print(stateFL)
bird: mockingbird
capital: Tallahassee
population: 19890000

There are lots of operators which can be overriden using metatables, including +, -, /, *, <, <=, and ==. You can also override the action when reading and writing to the table to provide different default values, tracking of all changes to the table, and read only tables.

Even though Lua has a mechanism for object oriented programming, you can use metatables to quickly create classes. The Lua book on-line shows how to create a Set class with just metatables. I’ve got a copy in the file Set.lua on my Github account.

The Set class is pretty cool and it points out another feature of Lua, that it’s a prototype rather than object oriented language. Since the first book covered Io, another prototype language, that’s not a new idea for me, but I’ll still finish up day 2 with finding out how Lua handles prototyping.

Advertisement

Leave a Comment

Day 2 begins: Lua Tables

A quick improvement. I complained about the code necessary to load a file into the REPL. The assert(loadfile('file.lua'))() seemed a bit long. There is a better way:

dofile('file.lua')

That’s more like it!

In writing this, I’ve come across numerous places where I’d like to add even more examples that I’ve created to try things out. If you ever want to try something for yourself, you don’t need to go to the trouble to install Lua (or, since you’re better than I am, build it for yourself). There’s an on-line interpreter with some demos at http://www.lua.org/cgi-bin/demo .

So, do you find that having one data structure that is indexed by integers and another that’s indexed by field names confusing? Would you prefer to be able to do:

a[3] = 4
a.name = 'Numbers'

Yep, Lua lets you! This is the table data structure that makes Lua worth learning.  Or so says Tate.

Table Basics

So, you can use tables for named fields as in:

stateFL = {
   capital = 'Tallahassee',
   bird = 'mockingbird',
   population = 19890000
}
stateFL.nickname = 'Sunshine State'

A quick function to step through all the entries is:

function print_table(t)
    for k, v in pairs(t) do
        print (k .. ": " .. v)
    end
end

And when printed you get:

population: 19890000
bird: mockingbird
capital: Tallahassee
nickname: Sunshine State

Okay, that looks like a dictionary like thing in many scripting languages. But then the fun begins.

You can also create an array with similar syntax:

citiesFL = {
   'Gainesville',
   'Jacksonville',
   'Miami',
   'Tampa'
}

citiesFL[5] = 'North Palm Beach'

And as output, get:

1: Gainesville
2: Jacksonville
3: Miami
4: Tampa
5: North Palm Beach

There is something a little surprising to those of us who have been in CS for way too long. The list begins at position 1, not position 0. Remember when we used to start counting with 1? Yeah, me neither.

Things get to be fun when you combine these, as in (using the previous state table):

stateFL[1] = 'Gainesville'
stateFL[2] = 'Jacksonville'
stateFL[3] = 'Miami'
stateFL[4] = 'Tampa'
stateFL[53] = 'Newberry'

Of course, there’s no need to have the numbers in consecutive order. You could even get crazy and add:

stateFL[-12] = 'Key West'

and if you really need to have an element at position 0, sure, go ahead:

stateFL[0] = 'Alachua'

It’s not like the data is stored in separate areas. In fact, the numbered data is mixed with the named data:

1: Gainesville
2: Jacksonville
3: Miami
4: Tampa
population: 19890000
-12: Key West
0: Alachua
53: Newberry
bird: mockingbird
capital: Tallahassee
nickname: Sunshine State

If you’re thinking “Well, no big deal, the number is just converted to a string,” nope. Florida is getting full, so let’s move north a bit:

citiesGA = {}
citiesGA[1] = 'Macon'
citiesGA[4] = 'Savannah'
citiesGA['1'] = 'Atlanta'
citiesGA['01'] = 'Richmond Hill

Printing this gives:

1: Macon
1: Atlanta
4: Savannah
01: Richmond Hill

A little more fun with the basics. You can access the named fields using the array notation as well, as in citiesFL["capital"] . And, just as with traditional arrays and dictionaries, this doesn’t need to be a string, so

item = 'capital'
print (citiesFL[item])

works just fine. And you do know that citiesFL.item is something completely different, right?

Still, this is not worth a whole new language. Not yet. Lua tables allow you to customize how the table works. Want to create a sparse matrix and have a different value returned? That’s where metatables come into play, but this is long enough for now, so that’ll have to wait until tomorrow.

Leave a Comment