Masters of the Lua Universe
Lua, a simple scripting language can be inspiring as it did for me. The best part of Lua is that if used properly, it can be a game changer, it can do things that one would have never thought of. It can be functional to an extent. Here's a lovely example I found written by randrews
I hope he does not mind me taking the code and talking about it here, after all it is available on Gist here . Well those that were here in for the ride can (or already have) clicked on the link and out of here.
For the rest that want to read on, well, here's why I found this amazing and interesting and what can we learn from this.
The code makes lua like Prolog. For those that do not know what PROLOG is, it is an older programming language that worked on relationship and predicates. It coul dbe used and the principles can still be used to create connections and AI. So how does it make the code look like Prolog? Think if we had code like this, would that work in Lua?
Not only that, you can add any more of these that you want, so for example
Obviously we do not have the functions defined for
The way it works is it hijacks the setmetatable function, or rather in other words it uses that for purposes other than just defining an OO class prototype. Generally, the
Let's see how this works. Start a new project and just type the following code and run it.
You will see that you see some output in the console before it gives you a Runtime Error about Global what not being present, etc.
So you see that setmetatable looks for everything that is passed, you can extend the language (in some ways) but that's for another day....
Now, we need to eliminate the Runtime Errors. So all we do is we use rawset as
we find that still we get a Runtime error as it does not find what, it is supposed to be a function. so what we do is we segregate if we have any word with an uppercase it is a variable and anything with a lowercase a function. So we can update our code as
This time when we run it, we get no errors, it all works just fine. However it is not very functional is it? This will still work with the combination of local variable, etc. So, to see it in action, let's try
Now back to our example, we need to set relationships using the functions and query it using the
If you see that relationships are one sided, so Luke is the Brother of Leia, but Leia is *NOT* the brother of Luke. So we have to set the two way friendship between Han and Luke. So you can see complex stuff like Betty -> likes Archie -> likes Veronica and Reggie -> likes Veronica. in the way we set up the setmetatable, all we need to do is
Fun, eh??? And to think that it is just one simple setmetatable function that can help you achieve all of that. Lua can do some really cool stuff, if we know how to tame it.
Thanks to randrews for this code.
I hope he does not mind me taking the code and talking about it here, after all it is available on Gist here . Well those that were here in for the ride can (or already have) clicked on the link and out of here.
For the rest that want to read on, well, here's why I found this amazing and interesting and what can we learn from this.
The code makes lua like Prolog. For those that do not know what PROLOG is, it is an older programming language that worked on relationship and predicates. It coul dbe used and the principles can still be used to create connections and AI. So how does it make the code look like Prolog? Think if we had code like this, would that work in Lua?
father(Vader, Luke) father(Vader, Leia) friend(Vader, Emperor) friend(Emperor, Vader) friend(Han, Luke) friend(Luke, Han) brother(Luke, Leia) sister(Leia, Luke) assert(is_father(Vader, Luke)) assert(is_sister(Leia, Luke)) assert(is_friend(Han, Luke)) assert(is_friend(Luke, Han)) assert(not is_friend(Vader, Luke)) assert(not is_friend(Han, Jabba))
Not only that, you can add any more of these that you want, so for example
company(Jayant,OZApps) language(Corona, Lua) language(Cocos2D, ObjectiveC) print(is_language(Corona, ObjectiveC)) print(is_company(Carlos, OZApps))
Obviously we do not have the functions defined for
father, friend, brother, sister and is_sister, is_father
, etc. This will therefore cause an error, but wouldn't it be fun (it is not very useful really) to have the functionality to have these functions created automatically? Impossible you say? That's what PROLOG was about and that is what we shall do in this example. Now do you see how cool and powerful LUA is or can be? How do we do that?The way it works is it hijacks the setmetatable function, or rather in other words it uses that for purposes other than just defining an OO class prototype. Generally, the
__index
method is what is used to check for the existence of a variable, so what we do is we use this method and create the relevant variables and connections.Let's see how this works. Start a new project and just type the following code and run it.
setmetatable(_G, {__index = function(globals, name) print(name) end }) what(was, this)
You will see that you see some output in the console before it gives you a Runtime Error about Global what not being present, etc.
So you see that setmetatable looks for everything that is passed, you can extend the language (in some ways) but that's for another day....
Now, we need to eliminate the Runtime Errors. So all we do is we use rawset as
setmetatable(_G, {__index = function(globals, name) print(name) rawset(globals, name, { }) end }) what(was, this)
we find that still we get a Runtime error as it does not find what, it is supposed to be a function. so what we do is we segregate if we have any word with an uppercase it is a variable and anything with a lowercase a function. So we can update our code as
setmetatable(_G, {__index = function(globals, name) print(name) if name:match("^[A-Z]") then -- entity rawset(globals, name, { }) else rawset(globals,name,function() end) end return rawget(globals,name) end }) what(Was, This)
This time when we run it, we get no errors, it all works just fine. However it is not very functional is it? This will still work with the combination of local variable, etc. So, to see it in action, let's try
setmetatable(_G, {__index = function(globals, name) print(name) if name:match("^[A-Z]") then -- entity rawset(globals, name, { }) else rawset(globals,name,function() end) end return rawget(globals,name) end }) what(Was, This) local Name="OZApps" Url = "http://www.oz-apps.com" new(Company,Name)You will see that in the console the variables, name and url are not displayed as they already exist in the context and need not be set for _G.
Now back to our example, we need to set relationships using the functions and query it using the
is_
prefix. Out setmetatable code changes a bit now tosetmetatable(_G,{ __index = function(globals, name) print(globals,name) if name:match("^[A-Z]") then -- entity rawset(globals, name, { }) else -- rule local rule = make_rule(name) rawset(globals, name, rule) end return rawget(globals, name) end }) function make_rule(name) return function(a, b) a[name .. "_of"] = b b[name] = a end end what(Was, This) local Name="OZApps" Url = "http://www.oz-apps.com" new(Company,Name)You will see that there is an error, as Name is local not available Globally in _G, so this will now work with non-local variables only here on (specifically for what we are attempting). Let us also add the
is_
prefix that will return the relationship.setmetatable(_G,{ __index = function(globals, name) print(globals,name) if name:match("^[A-Z]") then -- entity rawset(globals, name, { }) elseif name:match("^is_") then -- predicate local pred = make_predicate(name) rawset(globals, name, pred) else -- rule local rule = make_rule(name) rawset(globals, name, rule) end return rawget(globals, name) end }) function make_rule(name) return function(a, b) a[name .. "_of"] = b b[name] = a end end function make_predicate(name) --print("predicate") local rule = name:match("^is_(.*)") return function(a, b) return b[rule] == a end end father(Vader,Luke) father(Vader,Leia) brother(Luke,Leia) sister(Luke,Leia) friend(Han, Luke) friend(Luke,Han) print(is_father(Vader,Luke)) print(is_brother(Luke,Leia)) print(is_sister(Luke,Leia)) print(is_friend(Han, Luke)) print(is_friend(Luke,Han))
If you see that relationships are one sided, so Luke is the Brother of Leia, but Leia is *NOT* the brother of Luke. So we have to set the two way friendship between Han and Luke. So you can see complex stuff like Betty -> likes Archie -> likes Veronica and Reggie -> likes Veronica. in the way we set up the setmetatable, all we need to do is
-- The metatable code and the two functions here likes(Betty, Archie) likes(Archie, Veronica) likes(Reggie, Veronica) print(is_likes(Archie, Betty)) print(is_likes(Betty, Archie)) print(is_likes(Reggie, Veronica)) print(is_likes(Veronica, Archie))
Fun, eh??? And to think that it is just one simple setmetatable function that can help you achieve all of that. Lua can do some really cool stuff, if we know how to tame it.
Thanks to randrews for this code.
Extraordinary, I have been working with Lua quite some time now and, as a student, have just finished an 'introduction to logic programming through prolog' course. I had never thought that setmetatable could be used to achieve such cool stuff other than OOP. Very cool read, many thanks to both you (for making it a clear read) and randrews for the concept and code.
ReplyDeleteGreat job!