Classes - jpbubble/NIL-isn-t-Lua GitHub Wiki
Lua has no support for classes at all, but since Lua has been set up in a way that allows programmers to "cheat" their way around things Lua doesn't support, I could make NIL support classes, which is pretty cool, I think... 😉
Now I'm not fully going into the deep about how classes work under the hood, what I can say is that even in the prototype version of NIL, classes have a bit of a stricter approach than non-class stuff. Cool, huh?
Basic approach:
class MyClass
int age
string name
string gender
end
MyClass kid1
MyClass kid2
MyClass kid3
kid = new MyClass
kid1.name="John"
kid1.age=4
kid1.gender="boy"
kid2 = new MyClass
kid2.name="Cynthia"
kid2.age=5
kid2.gender="girl"
kid3 = new MyClass
kid3.name="William"
kid3.age=6
kid3.gender="boy"
Like most class-based languages, classes are reference based and not value based. Please note they are not just tables. Any kind of field can be added to a table, but classes are a lot more restrictive
kid3.mother = "Joyce"
That will result into an error, since there is no field named "mother" in this class. I need to note that the parser and translator will allow that syntax, and the error is thrown during runtime. Also this will also throw an error.
kid3.age="six"
Since 'age' may only contain a number.
Methods
Now classes also can contain methods. They can just be declared in the same manner as regular functions
class MyClass
int a = 0
void add(int addwith)
self.a = self.a + addwith
end
end
MyClass n
n = new MyClass
for i = 1,10 do
n.add(1)
print(n.a)
end
What should be noted is that "self" is always required inside a method (unlike much other languages). What you also may see is that (unlike what is usual in Lua) that you need a period "." and not a colon ":" to call methods. This only works for classes, if you just use a normal table the regular notation still applies.
Static fields
Static fields share their value with all variables of the same class.
class Test
int a
static int b
end
Test T1 = new Test
Test T2 = new Test
t1.a=1
t2.b=2
t2.a=3
t2.b=4
print(t1.a,t1.b) // output: 1 4
Now in the example above you can also do this
print(Test.b) // output: 4
Static functions/methods
Yes, this works too! Contrary to normal methods, they will not have the variable 'self' at their disposal and they can be called from both class variables as from the classes themselves.
class StFunc
static void MyFunc(string hello)
print(self) // does work, but will output 'nil'
print(hello)
end
void MyMethod(string hello)
print(self) // does work, now outputs the class var's reference
print(hello)
end
end
StFunc.MyFunc("Yo!")
StFunc F
F = new StFunc
F.MyFunc("Hi")
F.MyMethod("Hello")
Output:
nil
Yo!
nil
Hi
<whatever Lua uses for reference data>
Hello
Constructors
Yes, NIL has support for constructors. Please note, NIL is not as sophisticated as C++ or C# on this one, so overloading is not supported (Lua simply won't allow that), but it's dead easy. Just declare a non-static void method called "CONSTRUCTOR", and the best part is, it does allow parameters too.
class Demo
string myvar
void CONSTRUCTOR(string something)
self.myvar = something
print("Hey you created a class! Cool!")
end
end
Demo D
D = new Demo("Jeroen")
print(D.myvar)
Output:
Hey you created a class! Cool!
Jeroen
Read-Only field variables
These can be declared by putting the keyword 'readonly' prior to the variable declaration, and then a field variable can only be defined in the class declaration itself, or during while the CONSTRUCTOR is running during class creation (please note, although it's technically possible to call a constructor method after creation too, readonly fields are then not assignable anymore).
class TestReadOnly
int a
readonly int b
void CONSTRUCTOR(a,b)
self.a = a
self.b = b // legal now, since we are in the constructor stage.
end
end
TestReadOnly TRO
TRO = new TestReadOnly(1,2)
print(TRO.a,TRO.b) // outputs: 1 2
TRO.a = 3 // all fine!
TRO.b = 4 // causes an error!
Private and public elements of classes
By default all elements of a class are public, and thus freely accessible, however you may not always want that. Private elements are only accessible from within methods, but not by the scope outside the class.
There are two ways to define a private. Either by prefixing the element's name with an underscore, or with the keyword private
class ClassWithPrivates
int publicint = 10
int _privateint1 = 20
private int privateint2 = 39
void expose()
// Called from within a method, so no issues here!
print(self.publicint,self._privateint1,self.privateint2)
end
end
var CWP
CWP = new ClassWithPrivates
CWP.expose() // Works, privates only accessed by the method
print(CWP.publicint) // Works, this one is public
print(CWP._privateint1) // Error! Underscore, thus private!
print(CWP.privateint2) // Error! Explicitly declared as private, this private!
"get" and "set"
NIL has these two keywords to create "properties" or variables that are actually methods, but disguised as variables. get and set can be put in a variable declaration turning them into 'properties' and the next lines can just be handled like a regular method.
class prop_class
get int myprop
return 5
end
set int myprop
if value~=5
print("You thought you could set the value to "..value..", eh! Bad luck, I'll keep it at 5")
end
end
end
var p
p = prop_class()
p.myprop = 7 // output: "You thought you could set the value to 7, eh! Bad luck, I'll keep it at 5"
print(p.myprop) // output: 5
"set int myprop" could basically be seen as "void myprop(int value)" difference is that value is now defined by the = symbol and not by (). "get int myprop" can be seen as "int myprop()". Also the ability to use "self" within these properties will depend whether these properties are "static" or not.
.NEW() static method notice
In NIL versions older than 19.07.13 you were required to create new classes with the .NEW() method like this:
a = MyClass.NEW()
From version 19.07.13 you have to do it like this:
a = new MyClass
The .NEW() method still works, but has been deprecated since version 19.07.13, and may even be removed in version 21.xx.xx or later. Some of my older code may still contain .NEW() and that's why it's still there, but the usage of .NEW() is highly discouraged.
() with 'new' or not
That depends... if you have a CONSTRUCTOR that doesn't take any arguments then () is optional so these will both work!
a = new MyClass
b = new MyClass()
When the CONSTRUCTOR does take arguments then the () are required.
Using NIL classes in 'pure' Lua
Yes, that is entirely possible. Of course, creating and defining the class itself must be done in NIL, but once created pure Lua will be able to handle the entire class as it should, the only difference is that the keyword 'new' cannot be used anymore and that () will be required when creating a new entry.
myclass.nil:
class myclass
int a
int b
int c
void CONTRUCTOR(aa,ba,ca)
self.a=aa or 0
self.b=ba or 0
self.c=ca or 0
end
end
myclasstest.lua
NIL.Use("myclass")
test1 = myclass() -- no parameters
test2 = myclass(10,20,30) -- parameters
print(test1.a,test1.b,test1.c) -- outputs 0 0 0
print(test2.a,test2.b,test2.c) -- outputs 10 20 30
Why is "new" required in NIL and not in Lua?
First of all Lua doesn't have support for the "new" keyword. Second just typing a class name, like you can do in Lua would in NIL create a delegate function which is in pure Lua only possible by using the "function" keyword. The "new" keyword gets that conflict away from NIL, that's all.
Destructors
Another important thing about classes are destructors. Like constructors are called when memory is being allocated for a new class variable, destructors are called on the moment they are wiped from the memory. Lua uses a garbage collecting system. All data memory allocated by Lua will automatically be released whenever no variables or anything are assigned to that memory anymore. However, this only goes for data allocated by Lua itself, and not for the data underlying APIs called by Lua have created and managed. And sometimes you do want that stuff to be released as well or memory leaks can be inevitable. Destructors will help you to make that happen. Now creating destructors is as easy as constructors.
class MyClass
void CONSTRUCTOR()
print("Yo, guys! I've been created!")
end
void DESTRUCTOR()
print("OH NO! I've been destroyed!")
end
end
A few notes about destructors
- There is no telling when the destructor is being called. Lua does not release the memory immediately, but at the time it deems appropriate. This is done for performance reasons, as your program would otherwise slow down. The destructor is not called until Lua has really decided the time is right to clean your trash up.
- The DESTRUCTOR method must be a non-static void.
- You can declare parameters to a DESTRUCTOR, but they will all be ignored, so save yourself the trouble.
- The keyword "private" has no effect at all on a DESTRUCTOR when it comes to garbage collection. You may want to set it to private to prevent it from being called directly causing a big mess in the process.
- Of course DESTRUCTORS have access to the "self" variable. Should go without saying, but I'll just say it again to prevent confusion.
An important note about "self"
Right now you are always obligated to prefix members of classes with "self" when you call them in any way from the class itself. That was because it was a bit hard on me to make that done automatically, like in many class based languages, such as C# (where "this" is the equivalent of "self", but it's not required all the time). However I cannot rule out this will happen in the future and then your code can really do odd things when you do this:
int h = 4
class MyClass
int h = 5
void sh()
print(h)
end
end
var mc
mc = new MyClass()
mc.sh()
In the current versions of NIL, this will show '4', since it will take the 'h' variable from the ground scope, however if I manage to make 'self' no longer required in classes it will show '5' as that is then the value to follow. If you wanna prevent bugs with future versions, I recommend you not to do what I did in the example above.
One final note about classes
Classes are one of the very few things in NIL in which NIL linked some of its own code into the translation of your script in stead of pure Lua. For line numbers shown in errors and tracebacks that shouldn't matter as this code is not put in the translation itself. Only a function linking to that. This was unavoidable as Lua would otherwise not support classes the way NIL provides the functionality. This does mean that classes can cause slowdown compared to 'pure Lua'. Nothing serious, but still. Under normal usage nobody should notice, but if speed is your first concern, and every nanosecond counts (in that case you shouldn't be using Lua at all, though) one should be careful with them. A good tip in general "var" is always faster than any other datatype (not counting 'void'). Particularly in classes this can be a way to speed things up, a little.