30log, in extenso 30 Lines Of Goodness is a minified framework for object-orientation in Lua. It features named (and unnamed) classes, single inheritance and a basic support for mixins.
It makes 30 lines. No less, no more.
30log is Lua 5.1, Lua 5.2 compatible.
##Contents
- Download
- Installation
- Quicktour
- Chained initialisation
- Mixins
- Printing classes and objects
- Class Commons support
- Specification
- Source
- Benchmark
- Contributors
##Download
You can download 30log via:
###Bash
git clone git://github.com/Yonaba/30log.git###Archive
- Zip: 0.9.0 ( latest stable, recommended ) | older versions
- Tar.gz: 0.9.0 ( latest stable, recommended ) | older versions
###LuaRocks
luarocks install 30log ###MoonRocks
luarocks install --server=http://rocks.moonscript.org/manifests/Yonaba 30log##Installation Copy the file 30log.lua inside your project folder, call it using require function. It will return a single local function, keeping safe the global environment.
##Quicktour ###Creating a class Creating a new class is fairly simple. Just call the returned function, then add some properties to this class :
class = require '30log' Window = class () Window.x, Window.y = 10, 10 Window.width, Window.height = 100,100You can also make it shorter, packing the default properties and their values within a table and then pass it as a single argument to the class function :
class = require '30log' Window = class { width = 100, height = 100, x = 10, y = 10}###Named classes Classes can be named.
To name a class, you will have to set the desired name as a string value to the reserved key __name :
class = require '30log' Window = class () Window.__name = 'Window'This feature can be quite useful when debugging your code. See the section printing classes for more details.
###Instances
####Creating instances
You can easily create new instances (objects) from a class using the default instantiation method named new():
appFrame = Window:new() print(appFrame.x,appFrame.y) --> 10, 10 print(appFrame.width,appFrame.height) --> 100, 100There is a shorter version though. You can call new class itself with parens, just like a function :
appFrame = Window() print(appFrame.x,appFrame.y) --> 10, 10 print(appFrame.width,appFrame.height) --> 100, 100From the two examples above, you might have noticed that once an object is created from a class, it already shares the properties of his mother class. That's the very basis of inheritance. So, by default, the attributes of the newly created object will copy their values from its mother class.
Yet, you can init new objects from a class with custom values for properties. To accomplish that, you will have to implement your own class constructor. Typically, it is a method (a function) that will be called whenever the new() method is used from the class to derive a new object, and then define custom attributes and values for this object.
By default, 30log uses the reserved key __init as a class constructor.
Window = class { width = 100, height = 100, x = 10, y = 10} function Window:__init(x,y,width,height) self.x,self.y = x,y self.width,self.height = width,height end appFrame = Window:new(50,60,800,600) -- same as: appFrame = Window(50,60,800,600) print(appFrame.x,appFrame.y) --> 50, 60 print(appFrame.width,appFrame.height) --> 800, 600__init can also be a table with named keys.
In that case though, the values of each single object's properties will be taken from this table upon instantiation, no matter what the values passed-in at instantiation would be.
Window = class() Window.__init = { width = 100, height = 100, x = 10, y = 10} appFrame = Window:new(50,60,800,600) -- or appFrame = Window(50,60,800,600) print(appFrame.x,appFrame.y) --> 10, 10 print(appFrame.width,appFrame.height) --> 100, 100####Under the hood 30log classes are metatables of their own instances. This implies that one can inspect the mother/son relationship between a class and its instance via Lua's standard function getmetatable.
local aClass = class() local someInstance = aClass() print(getmetatable(someInstance) == aClass) --> trueAlso, classes are metatables of their derived classes.
local aClass = class() local someDerivedClass = aClass:extends() print(getmetatable(someDerivedClass) == aClass) --> true###Methods Objects can call their class methods.
Window = class { width = 100, height = 100, w = 10, y = 10} function Window:__init(x,y,width,height) self.x,self.y = x,y self.width,self.height = width,height end function Window:set(x,y) self.x, self.y = x, y end function Window:resize(width, height) self.width, self.height = width, height end appFrame = Window() appFrame:set(50,60) print(appFrame.x,appFrame.y) --> 50, 60 appFrame:resize(800,600) print(appFrame.width,appFrame.height) --> 800, 600Though, objects cannot be used to instantiate new objects.
appFrame = Window:new() aFrame = appFrame:new() -- Creates an error aFrame = appFrame() -- Also creates an error###Inheritance
A class can inherit from any other class using a reserved method named extends. Similarly to class, this method also takes an optional table with named keys as argument to include new properties that the derived class will implement. The new class will inherit his mother class properties as well as its methods.
Window = class { width = 100, height = 100, x = 10, y = 10} Frame = Window:extends { color = 'black' } print(Frame.x, Frame.y) --> 10, 10 appFrame = Frame() print(appFrame.x,appFrame.y) --> 10, 10A derived class can redefine any method implemented in its base class (or mother class). Therefore, the derived class still has access to his mother class methods and properties via a reserved key named super.
-- Let's use this feature to build a class constructor for our `Frame` class. -- The base class "Window" Window = class { width = 100, height = 100, x = 10, y = 10} function Window:__init(x,y,width,height) self.x,self.y = x,y self.width,self.height = width,height end -- A method function Window:set(x,y) self.x, self.y = x, y end -- A derived class named "Frame" Frame = Window:extends { color = 'black' } function Frame:__init(x,y,width,height,color) -- Calling the superclass constructor Frame.super.__init(self,x,y,width,height) -- Setting the extra class member self.color = color end -- Redefining the set() method function Frame:set(x,y) self.x = x - self.width/2 self.y = y - self.height/2 end -- An appFrame from "Frame" class appFrame = Frame(100,100,800,600,'red') print(appFrame.x,appFrame.y) --> 100, 100 -- Calls the new set() method appFrame:set(400,400) print(appFrame.x,appFrame.y) --> 0, 100 -- Calls the old set() method in the mother class "Windows" appFrame.super.set(appFrame,400,300) print(appFrame.x,appFrame.y) --> 400, 300###Inspecting inheritance
class.is can check if a given class derives from another class.
local aClass = class() local aDerivedClass = aClass:extends() print(aDerivedClass:is(aClass)) --> trueIt also returns true when the given class is not necessarily the immediate ancestor of the calling class.
local aClass = class() local aDerivedClass = aClass:extends():extends():extends() -- 3-level depth inheritance print(aDerivedClass:is(aClass)) --> trueSimilarly instance.is can check if a given instance derives from a given class.
local aClass = class() local anObject = aClass() print(anObject:is(aClass)) --> trueIt also returns true when the given class is not the immediate ancestor.
local aClass = class() local aDerivedClass = aClass:extends():extends():extends() -- 3-level depth inheritance local anObject = aDerivedClass() print(anObject:is(aDerivedClass)) --> true print(anObject:is(aClass)) --> true##Chained initialisation In a single inheritance tree, the __init constructor can be chained from one class to another.
This is called initception.
And, yes, it is a joke.
-- A mother class 'A' local A = Class() function A.__init(instance,a) instance.a = a end -- Class 'B', deriving from class 'A' local B = A:extends() function B.__init(instance, a, b) B.super.__init(instance, a) instance.b = b end -- Class 'C', deriving from class 'B' local C = B:extends() function C.__init(instance, a, b, c) C.super.__init(instance,a, b) instance.c = c end -- Class 'D', deriving from class 'C' local D = C:extends() function D.__init(instance, a, b, c, d) D.super.__init(instance,a, b, c) instance.d = d end -- Creating an instance of class 'D' local objD = D(1,2,3,4) for k,v in pairs(objD) do print(k,v) end -- Output: --> a 1 --> d 4 --> c 3 --> b 2The previous syntax can also be simplified, as follows:
local A = Class() function A:__init(a) self.a = a end local B = A:extends() function B:__init(a, b) B.super.__init(self, a) self.b = b end local C = B:extends() function C:__init(a, b, c) C.super.__init(self, a, b) self.c = c end local D = C:extends() function D:__init(a, b, c, d) D.super.__init(self, a, b, c) self.d = d end##Mixins
30log provides a basic support for mixins. This is a powerful concept that can be used to implement a functionality into different classes, even if they do not have any special relationship.
30log assumes a mixin to be a table containing a set of methods (function).
To include a mixin in a class, use the reserved key named include.
-- A mixin Geometry = { getArea = function(self) return self.width, self.height end, resize = function(self, width, height) self.width, self.height = width, height end } -- Let's define two unrelated classes Window = class {width = 480, height = 250} Button = class {width = 100, height = 50, onClick = false} -- Include the "Geometry" mixin inside the two classes Window:include(Geometry) Button:include(Geometry) -- Let's define objects from those classes local aWindow = Window() local aButton = Button() -- Objects can use functionalities brought by the mixin. print(aWindow:getArea()) --> 480, 250 print(aButton:getArea()) --> 100, 50 aWindow:resize(225,75) print(aWindow.width, aWindow.height) --> 255, 75Note that, when including a mixin into a class, only methods (functions, actually) will be imported into the class. Also, objects cannot include mixins.
aWindow = Window() aWindow:include(Geometry) -- produces an error##Printing classes and objects Any attempt to print or tostring a class or an instance will return the name of the class as a string. This feature is mostly meant for debugging purposes.
-- Let's illustrate this, with an unnamed __Cat__ class: -- A Cat Class local Cat = class() print(Cat) --> "class(?):<table:00550AD0>" local kitten = Cat() print(kitten) --> "object(of ?):<table:00550C10>"The question mark symbol ? means here the printed class is unnamed (or the object derives from an unnamed class).
-- Let's define a named __Cat__ class now: -- A Cat Class local Cat = class() Cat.__name = 'Cat' print(Cat) --> "class(Cat):<table:00411858>" local kitten = Cat() print(kitten) --> "object(of Cat):<table:00411880>"##Class Commons Class-Commons is an interface that provides a common API for a wide range of object orientation libraries in Lua. There is a small plugin, originally written by TsT which provides compatibility between 30log and Class-commons.
See here: 30logclasscommons.
##Specification
You can run the included specs with Telescope using the following command from the root foolder:
lua tsc -f specs/* ###Source
###30logclean 30log was initially designed for minimalistic purposes. But then commit after commit, I came up with a source code that was obviously surpassing 30 lines. As I wanted to stick to the "30-lines" rule, I had to use an ugly syntax which not much elegant, yet 100 % functional.
For those who might be interested though, the file 30logclean.lua contains the full source code, properly formatted and well indented for your perusal.
###30logglobal
The file 30logglobal.lua features the exact same source as the original 30log.lua, excepts that it sets a global function named class. This is convenient for Lua-based frameworks such as Codea.
##Benchmark
Performance tests featuring classes creation, instantiation and such have been included. You can run these tests with the following command with Lua from the root folder, passing to the test script the actual implementation to be tested.
lua performance/tests.lua 30logFind here an example of output.
##Contributors
- TsT2005, for the original Class-commons support.
##License This work is under MIT-LICENSE
Copyright (c) 2012-2014 Roland Yonaba
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
