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?

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 to

setmetatable(_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.

Comments

  1. 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.

    Great job!

    ReplyDelete

Post a Comment

Popular Posts