31. A lesson from the roller coaster demo
I probably shouldn’t have done this, but I had a look at the roller coaster demo code to see how much of it I could figure out. As I expected, the math is fearsome, but I picked up some interesting things to share with you. The first one – using a table of functions instead of a class, was so interesting, that it takes up all of this post.
Tables of classes
The first interesting thing is the Colour tab. It is not a class, but a table of functions. This is a very powerful concept, so let’s explore it. First we’ll look at how it works, then we’ll ask the question – when would you do this instead of using a class? And why do it at all?
Colour = {} --defines a table Colours Colour.ModifyAlpha = false --adds an item to the table function Colour.blend(cc,t,c) --adds a function to the table --code here end
First, we define a table as normal. We can add properties to it, like ModifyAlpha above (if this were a class, we would be writing self.ModifyAlpha in the init function). It’s cool that you can add properties outside of functions.
Then we can add functions, like blend above, because tables can contain anything (except nil). Below is how we might use that function in our code – note how we use a dot, not a colon, in Colour.blend. I’ll explain a little later.
function Test() c = Colour.blend(color1,0.5,color2) end
The advantage of this “table” approach is that all the colour code is contained in its own table, whether it be constants like ModifyAlpha, or functions like blend. It was written so it could be dropped into any project without clashing with the existing code. Now imagine if you hadn’t put this code into a table, and you include it in a project which already has a blend function. You have a nasty conflict and a possible mess. By wrapping it up in its own table, you seal it off from any other code. And it is extremely simple to do.
So now we have an alternative to classes. But when should you use each of them? Let’s take two situations to illustrate.
Standard function library
Suppose we write a function library that does complicated calculations. Every function takes some parameters and returns a result. The library
- doesn’t need to “remember” anything because each time you call a function, you provide everything it needs.
- is unique, ie there is only one copy
- doesn’t belong to any particular object
- behaves exactly the same no matter who or what is calling it
.
The table approach above suits this library very well. To see why, let’s look at a different example.
Individual function libraries
Suppose we want to bounce some balls around the screen. Although they will be much the same, and they will all behave the same way in terms of bounciness and gravity, they might each have their own colour, and they will each need to know their x,y position, angle and speed at all times.
So now we need a set of functions that describe how a single ball behaves, and then we need to get it to manage each ball. We could do this with a table of functions as above, but we would have to hold a table of ball positions, velocities etc in our Main tab, and we would be continually passing through all this data to the functions in our library, because the library wouldn’t be remembering anything.
It makes much more sense to store the data for each ball in its own separate copy of the library, then our code is far cleaner. So we create a ball class, and make a copy for each ball and send through the data for that ball.
Any time we want to do anything with a ball, we call the function we want, using a colon – eg ball[3]:draw() – so that the class knows which ball is talking to it (remember from my posts 6&7, when you use a colon, all you are doing is passing through a hidden variable “self” that lets Codea know which ball is calling).
So when should you use tables or classes?
So we can sum it up like this –
- if your function library needs to know who is calling it, because it is storing information for multiple objects, then you use a class
- If your function library doesn’t store data for objects, and doesn’t care who is calling, because the function parameters give it everything it needs, then a table of functions works fine
.
To illustrate the amazing flexibility of Codea, you can mix classes and tables together. If you really want to understand this, have a look at the example below, where there are two volume functions,
- volume defined with a colon
- volume2, defined with a dot
Neither function uses self. All the function needs to know is radius.
Ball=class{} function Ball:volume(r) --colon return 4/3*math.pi*r*r*r end function Ball.volume2(r) --dot return 4/3*math.pi*r*r*r end --Main function setup() b1=Ball() --now do some test prints to see what happens print('1.colon',b1:volume(10)) --success print('2.dot',b1.volume2(10)) --success print('3.dot+no self',b1.volume(10)) --error print('4.colon+self(first)',b1:volume(nil,10))--error print('5.colon+self(last)',b1:volume(10,nil))--success end
Taking the print lines in order
- call volume with a colon, normally – success
-
call volume2, with the dot – success
So – I can mix dot and colon functions in a class, but being mischievous, I want to know if I can call volume (defined with a colon) using a dot – so the remaining print lines are experiments to try to understand how Code deals with this.
- call volume with a dot instead of a colon. Error! The reason is that Codea is expecting a “self” variable (the volume function has a colon, remember), and allocated my parameter 10 to self, leaving nothing for r, so the error was that r was nil.
-
ok, let’s include a dummy self variable to keep Codea happy. Apparently, self is included as the first variable, so we’ll include a nil variable before our radius. Error! Our radius is still nil. Hmm
-
Let’s swap the variables around and our radius first, and the dummy self variable second. Success!
So the result is that you can mix dot and colon functions in a class, and even call colon functions with dots, as long as you’re very careful how you use them.
I don’t know about you, but I learned something from all this, especially the usefulness of tables for storing function libraries. And I’ve only scratched the surface of the roller coaster.
Thanks CoolCodea! I recently started using a table to hold my custom library function this helped resolve naming conflicts. I have a question for you. I’ve bee writing my functions in my table differently.
I use:
hlib = {}
hlib.testFunction = function(x)
print(x)
end
whats the difference from your method above? As in:
hlib = {}
function hlib.testFunction(x)
print(x)
end
Thanks
No real difference as far as I know, except I think the method used in the roller coaster is a little easier to read