This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
augustas656's profile picture

Can't wrap head around prototyping and metatables, how to implement this Java example?

Started by augustas656, 20 February 2016 - 05:23 PM
augustas656 #1
Posted 20 February 2016 - 06:23 PM
Hello,

So I have been trying to study the Lua implementation of prototypes, how to emulate classes and metatables but there are many questions that I have left unanswered and many of the examples shown are not explained thoroughly, in enough detail to say, what would happen if I were to remove this one short line of code from the prototyping example.

Since I am coming from a very fluent Java background, even though I do not expect everyone who is experienced with Lua and computer craft to know the language, I wrote a java example to see if anyone could answer what can be implemented from it to Lua and hopefully how?


public interface Transport {
public int getFuel();
public void refuel(int fuel);
public void driveTo(int x, int y);
};
public abstract class Vehicle implements Transport {
public final String brand;
public final String model;
public int fuel = 0;

public Vehicle(String brand, String model) {
  this.brand = brand;
  this.model = model;
}

public int getFuel() { return fuel; }
public int refuel(int fuel) { this.fuel += fuel; }
}
public class Car extends Transport {
public static int wheels;

public Car(String brand, String model) {
  super(brand, model);
}

public void driveTo(int x, int y) {
  // some stuff affecting fuel
}
}

Also, can you have multiple inheritance?

Kind Regards,
augustas656
Dragon53535 #2
Posted 20 February 2016 - 08:30 PM
You can't easily go for an object oriented approach without messing with metatables, however you can prototype in a way.

local myFunc
local function callMyFunc()
  return myFunc("Hello")
end
myFunc = function(var1)
  print(var1)
end

callMyFunc()
augustas656 #3
Posted 20 February 2016 - 08:53 PM
Yes yes I know you have to prototype and use metatables but I'm asking is that, can everything I have written in the Java code that I have written be implemented into Lua with exact or similar functionality and how would one go about doing said specific features such as interfaces, classes, class variables etc. I have looked at the Lua docs for doing meta-programming but I cannot map how each line of code will achieve object orientation, like I couldn't tell at all what removing some lines would do to the functionality of the code such as self.__index = self in the constructor, what? But please know that this is not the only problem and I can't see how the entire code fits together to make object orientation happen, I'm just trying to understand Lua's prototyping and how to achieve object orientation.

P.S. in the java code, Car should extend Vehicle not Transport

Kind Regards,
augustas656
Edited on 20 February 2016 - 07:54 PM
MKlegoman357 #4
Posted 20 February 2016 - 09:45 PM
Lua is more like JavaScript: even though it doesn't have a class system it gives you tools to create your own. In Lua, you can implement OOP in any way you want. Your class system can support operator overloading, function overloading, multiple inheritance and any other features you might think about. Since most of your OOP system will need tables and probably metatables you'll want to learn those. Tables are the best feature of Lua. Metatables allow you to extend them even more.

Both, tables and metatables, should actually be easy to understand, since they are quite basic (but powerful) things. A table is simply a container, where you can store any value under any key. Because of the lack of OOP, tables and functions can actually work together using the "colon" syntax:


local object = { --# define a simple object
  bar = "Hello World!";
}

function object:foo () --# create a function inside that object
  print(self.bar)
end

object:foo() --# prints 'Hello World!'

You can extend tables using metatables. Metatables define what a table should do in certain situations. For example if, when assigning a new value to a table:


local object = {}

object.foo = 1

..you'd want to modify that value before actually assigning it, you could use the '__newindex' metamethod:


local object = setmetatable({}, { --# create a table and change it's metatable
  __newindex = function (t, k, v) --# define the metamethod'
    rawset(t, k, v + 1) --# modify the value
  end;
})

object.foo = 1

print(object.foo) --# prints '2'
KingofGamesYami #5
Posted 20 February 2016 - 10:57 PM
I made a weird sort of class thingy here, it's probably buggy and doesn't implement interfaces or anything like that.

Oeed has something interesting here that has a couple of bugs. He's constantly fixing bugs and redoing the entire thing though, so I wouldn't expect it to work 100%.
augustas656 #6
Posted 21 February 2016 - 07:38 PM
Can you assign multiple metatables to one table?

Edit: also is the "self" identifier reserved or only assigned with the colon operator?

Kind Regards,
augustas656
Edited on 21 February 2016 - 06:59 PM
MKlegoman357 #7
Posted 21 February 2016 - 08:12 PM
A table can only have one metatable, which should be enough. When defining a function like this:


local object = {
  bar = "test";
}

function object:foo (param)
  print(self.bar .. " " .. param)
end

It is equivalent to this:


local object = {
  bar = "test";
}

object.foo = function (self, param)
  print(self.bar .. " " .. param)
end

The color operator automagically adds the 'self' parameter as the first argument to the function. And no, the 'self' identifier is not a reserved keyword.
Edited on 21 February 2016 - 07:13 PM
augustas656 #8
Posted 21 February 2016 - 09:55 PM
Okay so I've attempted making a class function to create classes, but my test results are a bit weird:
http://pastebin.com/dVJEnFE1

And the results are 10 and a table address, I expect 20 and 10 instead…

Kind Regards,
augustas656
KingofGamesYami #9
Posted 21 February 2016 - 10:00 PM
__call - Treat a table like a function. When a table is followed by parenthesis such as "myTable( 'foo' )" and the metatable has a __call key pointing to a function, that function is invoked (passing the table as the first argument, followed by any specified arguments) and the return value is returned

http://lua-users.org/wiki/MetatableEvents

Your __call looks like this:


__call = function(...)
            constructor(prototype, ...)
            return prototype;
        end

…which means you're calling constructor with 4 arguments. prototype, the table, width, and height.
Edited on 21 February 2016 - 09:06 PM
augustas656 #10
Posted 21 February 2016 - 10:19 PM
__call - Treat a table like a function. When a table is followed by parenthesis such as "myTable( 'foo' )" and the metatable has a __call key pointing to a function, that function is invoked (passing the table as the first argument, followed by any specified arguments) and the return value is returned

http://lua-users.org...MetatableEvents

Your __call looks like this:


__call = function(...)
			constructor(prototype, ...)
			return prototype;
		end

…which means you're calling constructor with 4 arguments. prototype, the table, width, and height.

http://pastebin.com/gzDpxf1Q

Ok updated, should this have any problems?

I am concerned about when I do Rectangle(10, 20); the prototype's default w and h are also changed, but those values being default, they should stay the same unless specifically changed as done in the constructor, will this happen?

Edit: nevermind it does happen, how do I prevent this, do I need to create a custom function to copy prototype & supertype or what should I do?
Edit 2: would it best if I did not declare default values in the prototype and let the constructor handle that, rather the prototype will be more like a static field and static function container?

Kind Regards,
augustas656
Edited on 21 February 2016 - 09:22 PM
MKlegoman357 #11
Posted 21 February 2016 - 10:40 PM
The problem is that the 'self' argument passed to the constructor is the 'Rectangle' object itself. It's not creating a new table for you, it's simply passing the 'Rectangle' table. In the object's initialization (the __call metamethod) you should crate a new table, setup it's metatable and then call the constructor on that table.
Edited on 21 February 2016 - 09:40 PM
augustas656 #12
Posted 21 February 2016 - 11:01 PM
The problem is that the 'self' argument passed to the constructor is the 'Rectangle' object itself. It's not creating a new table for you, it's simply passing the 'Rectangle' table. In the object's initialization (the __call metamethod) you should crate a new table, setup it's metatable and then call the constructor on that table.

But if you look at my code for the class function, all the function does is create an empty table that sets the metatable to all the fields that I would like the constructor to access via __index. Which is exactly what you're telling me to do, but from my understanding even if I create a new table setting the __index to rectangle will still change rectangle's values if I do so within the constructor.

Kind Regards,
augustas656
augustas656 #13
Posted 21 February 2016 - 11:12 PM
Oh never mind I made it with a new empty instance and it worked, however I wanted to ask, how would one call a super-constructor if inherited?

Kind Regards,
augustas656
KingofGamesYami #14
Posted 21 February 2016 - 11:13 PM
If you want to control setting things in your table, you should use a blank table and assign an __newindex metamethod

__newindex - Control property assignment. When calling "myTable[key] = value", if the metatable has a __newindex key pointing to a function, call that function, passing it the table, key, and value.
  • Use "rawset(myTable,key,value)" to skip this metamethod.
  • (If the __newindex function does not set the key on the table (using rawset) then the key/value pair is not added to myTable.)

http://lua-users.org/wiki/MetatableEvents

Edit: For the super() constructer, you could do something similar to what I did in my example I posted earlier. I control the environment of the functions and insert certain variables. For instance, within my objects I can use this instead of self.

relevant part of my class

    __index = function( t, k )
        local temp = rawget( getmetatable( t ).realValues, k )
        local result = temp ~= nil and temp or getmetatable( t ).__super[ k ]
        if type( result ) == "function" then
            local env = getfenv( result )
            env.this, env.super = t, getmetatable( t ).__super
            setfenv( result, env )
        end
        return result
    end,
Edited on 21 February 2016 - 10:17 PM
augustas656 #15
Posted 21 February 2016 - 11:21 PM
If you want to control setting things in your table, you should use a blank table and assign an __newindex metamethod

__newindex - Control property assignment. When calling "myTable[key] = value", if the metatable has a __newindex key pointing to a function, call that function, passing it the table, key, and value.
  • Use "rawset(myTable,key,value)" to skip this metamethod.
  • (If the __newindex function does not set the key on the table (using rawset) then the key/value pair is not added to myTable.)

http://lua-users.org...MetatableEvents

Edit: For the super() constructer, you could do something similar to what I did in my example I posted earlier. I control the environment of the functions and insert certain variables. For instance, within my objects I can use this instead of self.

relevant part of my class

	__index = function( t, k )
		local temp = rawget( getmetatable( t ).realValues, k )
		local result = temp ~= nil and temp or getmetatable( t ).__super[ k ]
		if type( result ) == "function" then
			local env = getfenv( result )
			env.this, env.super = t, getmetatable( t ).__super
			setfenv( result, env )
		end
		return result
	end,

Well what I want to have for inheritance is that in the class function there are parameters (constructor, class, …) the … are superclasses, their prototypes should just be part of the index function, which is part of the class's index function, so they are looked-up, but as for super-constructors, for now I should say, they only need to be optionally used, but they can only exist within the constructor. I am not sure how to enable access to separate super-classes super-constructors.

Kind Regards,
augustas656
Edited on 21 February 2016 - 10:23 PM
KingofGamesYami #16
Posted 21 February 2016 - 11:28 PM
Set the environment of the constructer function to include the super's constructer. You'll have to store the constructer function of the superclass somewhere, probably in the metatable (you can put anything in there).
augustas656 #17
Posted 21 February 2016 - 11:40 PM
Would there be any disadvantages to sending and handling constructed instances of super-classes to inherit from? Similar to how this is done in Scala, but you don't have to look it up.

Kind Regards,
augustas656
augustas656 #18
Posted 22 February 2016 - 07:23 PM
Okay so I have written one implementation but I'm getting wrong results, I'm wondering how this could be fixed?


function class(constructor, class)
constructor = constructor or {}
class = class or {}

function class:new(...)
  local self = setmetatable({}, {
   __index = setmetatable(class, getmetatable(self));
  });

  constructor(self, ...);
  return self;
end
return class;
end
Rectangle = class(function(self, w, h)
self.w = w;
self.h = h;
end, { testA = 1 });
Square = class(function(self, size)
Rectangle.new(self, size, size);
end, { testB = 4 });
rect = Rectangle:new(2, 3);
square = Square:new(5, 6);
print(rect.w);
print(rect.h);
print(rect.testA);
print(rect.testB);
print(square.w);
print(square.h);
print(square.testA);
print(square.testB);

Gives me the results of 2, 3, 1, 4, nil, nil, nil, 4
And expected results of 2, 3, 4, nil, 5, 6, 1, 4

Because square inherits rectangle. And this implementation should allow chaining as many inheritances as you desire.

Edit: So I re-tried with another version but now I'm getting error at print(rect.testA); and new code:


function class(construct, namespace)
construct = construct or {};
namespace = namespace or {};

function namespace:new(...)
  arg_metatable = getmetatable(self) or {};
  arg_supers = arg_metatable.supers or {};
  arg_supers[#arg_supers + 1] = namespace;

  local self = setmetatable({}, {
   supers = arg_supers;
   __index = function(object, key)
	for _, super in pairs(supers) do
	 if super[key] then return super[key] end;
	end
   end
  });

  construct(self, ...);
  return self;
end
return namespace;
end
Rectangle = class(function(self, w, h)
self.w = w;
self.h = h;
end, { testA = 1 });
Square = class(function(self, size)
Rectangle.new(self, size, size);
end, { testB = 4 });
rect = Rectangle:new(2, 3);
square = Square:new(5, 6);
print(rect.w);
print(rect.h);
print(rect.testA);
print(rect.testB);
print(square.w);
print(square.h);
print(square.testA);
print(square.testB);

Edit: yet another edit with a clearer updated version, this version though gives me the same error but without even printing rect.w and rect.h which should be 2 and 3, rather I get the error about nil rather than table in code "for _, inherit in pairs(inherits) do" straight away.


function class(construct, namespace)
    construct = construct or {};
    namespace = namespace or {};

    -- object can be a class or an instance to inherit from
    namespace.new = function(object, ...)
        object_metatable = getmetatable(object) or {};
        object_inherits = object_metatable.inherits or {};

        new_inherits = object_inherits
        new_inherits[#new_inherits + 1] = namespace;
        new_instance = {};

        setmetatable(new_instance, {
            inherits = new_inherits,
            __index = function(instance, key)
                for _, inherit in pairs(inherits) do
                    if inherit[key] then return inherit[key] end
                end
            end
        });

        return new_instance;
    end

    return namespace;
end

Rectangle = class(
    function(self, w, h)
        self.w = w;
        self.h = h;
    end,
    { testA = 1 }
);

Square = class(
    function(self, size)
        Rectangle.new(self, size, size);
    end,
    { testB = 4 }
);

rect = Rectangle:new(2, 3);
square = Square:new(5, 6);

print(rect.w);
print(rect.h);
print(rect.testA);
print(rect.testB);

print(square.w);
print(square.h);
print(square.testA);
print(square.testB);

Kind Regards,
augustas656
Edited on 22 February 2016 - 09:07 PM
Bomb Bloke #19
Posted 23 February 2016 - 01:33 AM
Your "clearer" version never seems to call your "construct" functions. This means the arguments you pass to the "new" function are entirely ignored, for one thing.

Additionally, if your "square" construct function were called, it'd in turn call Rectangle.new… and then entirely ignore whatever the return value is (as you're not assigning it to anything).

Though it looks to me like this may not be an exact copy of what you're running, as you've got a line incorrectly ending with a comma (which'll lead to a compiler error before the code even gets to run).

One of your other issues comes down to variable localisation. You aren't using it at all, meaning every __index function you create is using the same "inherits" global - all objects you create will end up using the inheritance table of the last created object. Have a read up on scope.

And you seem to've neglected to mention what any of your errors say?
Edited on 23 February 2016 - 12:36 AM
augustas656 #20
Posted 23 February 2016 - 10:41 PM
Is there a way to chain new objects that have their own metatables as the __index of other objects for multiple times like chaining instances of super-classes through the hierarchy to the base class? I have re-written the best implementation of OOP for my needs that I could think of but I would greatly appreciate tips.

Stub:


function class(super, namespace, constructor)
setmetatable(namespace, {
  __call = function(self, ...)
 
  end
});
end

To create a class one must set the class variable to the return value of the "class" function, to which you pass arguments (super, namespace, constructor). The super argument is a set of parent class instances, the namespace argument is a table for the class variables and class functions, the constructor argument is a function that sets instance variables and instance functions to self. The "class" function returns the namespace with a new meta-table granting functionality:

The namespace should have access to parent class functions and class variables through the index meta-method and the ability to override them, which would also change what other parent class functions refer to when referring to the parent class function or variable. The constructor should be called by the call meta-method of the namespace and passed in a new empty table as the first argument called self with a meta-table containing the index meta-method that would allow access to parent class and class instance functions and variables, and allow overriding them. When I say override and say variable I mean assign the variable, because functions can be expressed as lambda values that can be stored in a variable, the storing process is still called assigning.

I think this functionality very closely imitates Ruby's object orientation. Any similar object orientation implementations for Lua like this out there, any advise or tips on making this or pieces of code are all greatly appreciated.

Kind Regards,
augustas656
KingofGamesYami #21
Posted 23 February 2016 - 11:05 PM

local a = {}
setmetatable( a, {__index ={x=1}} )
print( a.x )
--#output: 1
local b = {}
setmetatable( b, {__index=a} )
print( b.x )
--#output: 1
Edited on 23 February 2016 - 10:40 PM
augustas656 #22
Posted 23 February 2016 - 11:08 PM

local a = {}
setmetatable( a, {__index ={x=1}} )
print( a.x )
--#output: 1
local b = {}
setmetatable( a, {__index=b} )
print( b.x )
--#output: 1

The second output is nil because b has not defined x and b does not have a meta-table. The second setmetatable statement changes a's metatable from __index={x=1}; to b (which is {}, nothing).

Kind Regards,
augustas656
KingofGamesYami #23
Posted 23 February 2016 - 11:42 PM
The second output is nil because b has not defined x and b does not have a meta-table. The second setmetatable statement changes a's metatable from __index={x=1}; to b (which is {}, nothing).

Wait what. Ok that was stupid… Edited it.

*note to self: do not write code on phone*
augustas656 #24
Posted 24 February 2016 - 09:32 PM
Apart from localisation of identifiers, I have realized where my main recurring problem lied, I thought that, metamethods within a metatable could reference each other and other functions and variables defined within the same metatable, but this is not the case as most metamethods pass in the table as the first argument which I now called self and I had to getmetatable(self) to access them this way. Thanks for all the help!

Kind Regards,
augustas656