inheritance and sub-tables

classic Classic list List threaded Threaded
13 messages Options
Reply | Threaded
Open this post in threaded view
|

inheritance and sub-tables

arioch82
This post was updated on .
Hi guys,

I have found some problems using an inheritance implementation in lua when my object contains other tables and i was wondering if there's a better way to handle it.

In short:
 - i have a basic "Class" definition that provides a New method to create instances/subclasses
 - I have a subclass of Class called "Base" that contains some attributes and a table with other values in it
 - I have two subclasses of Base called "A" and "B" that are changing the default values for attributes defined in Base and its inner table
 - When i create instances of these "A" and "B" classes i have found that even if A and B are supposed to be separate things, the changes in "B" (being the last definition) have actually altered the "Base" class definition and so are affecting objects created by "A".

The best way to explain it is through an example:

--------------------------------------------------------------

local luaprint = print

-- Class Template
local Class = {
    New = function (self, obj)
        local obj = obj or {}
        setmetatable(obj, self)
        self.__index = self
        return obj
    end
}

-- Base Class
local Base = Class:New {
    name = "Base",
    v1 = 1,
   
    --------- OPTION  1 ---------
    test = {
        v2 = -1
    },
    --------- OPTION  1 ---------

    print = function(self)
        luaprint("name = " .. self.name .. ", v1 = " .. self.v1)

        self.test.print()
        self.test:print_v()
    end
}

function Base.test.print()
    luaprint("Class Base")  
end

function Base.test.print_v(self)
    luaprint("v2 = " .. self.v2)  
end

-- First Class

local A = Base:New {
    name = "A",
    v1 = 2,
}

A.test.v2 = -2

function A.test.print()
    luaprint("Class A")
end

-- Second Class

local B = Base:New {
    name = "B",
    v1 = 3,
}

B.test.v2 = -3

function B.test.print()
    luaprint("Class B")  
end

-- prints
print("---- BASE")
local var = Base:New{}
var:print()

print("---- A")
var = A:New{}
var:print()

print("---- B")
var = B:New{}
var:print()
--------------------------------------------------------------


Now the above code gives this output

--------- OUTPUT 1 ---------
---- BASE
name = Base, v1 = 1
Class B
v2 = -3
---- A
name = A, v1 = 2
Class B
v2 = -3
---- B
name = B, v1 = 3
Class B
v2 = -3
--------- OUTPUT 1 ---------

as you can see when B changes an element in the subtable "test" that value will change for ALL instances.

A Fix for this is redefining the Base like this:

--------------------------------------------------------------

-- Base Class
local Base = Class:New {
    name = "Base",
    v1 = 1,
   
    --------- OPTION  2 ---------
    test = Class:New {
        v2 = -1
    },
    New = function(self, obj)
        local obj = Class.New(self, obj)
        obj.test = self.test:New{}
        return obj
    end,
    --------- OPTION  2 ---------

    print = function(self)
        luaprint("name = " .. self.name .. ", v1 = " .. self.v1)

        self.test.print()
        self.test:print_v()
    end
}
--------------------------------------------------------------


which then gives the expected output


--------- OUTPUT 2 ---------
---- BASE
name = Base, v1 = 1
Class Base
v2 = -1
---- A
name = A, v1 = 2
Class A
v2 = -2
---- B
name = B, v1 = 3
Class B
v2 = -3
--------- OUTPUT 2 ---------


I believe this happens because all tables are referenced whilst other types are actually copied but i was wondering if there's any way to automate the process / handle this in a better way as it's something that seems really prone to errors that are hard to track down.

Thank you
Reply | Threaded
Open this post in threaded view
|

Re: inheritance and sub-tables

Javier Guerra Giraldez
On Wed, Feb 26, 2014 at 1:45 PM, arioch82 <[hidden email]> wrote:
> I have found some problems using inheritance in lua when my object contains
> other tables and i was wondering if there's a better way to handle it.


AFAICT, this is not about "inheritance in Lua", because there's not
such a thing.  it's about this specific inheritance implementation.

your example, in short:
- defines a table 'Class' with a method 'New'
- uses Class:New() to create 'Base' table and sets a 'test' field
holding a table.  note that this is a field of Base
- uses Base:New() to create 'A'
- uses Base:New() to create 'B'

Now, you use the name 'Class', so i guess you're trying to approximate
a class-based OOP.  therefore, you have in mind classes and instances.
 But what is 'Base'?  is it an instance? or is it a class?  or maybe
it's an instance of 'Class', so it's 'a class'?  if so, i think you're
rediscovering prototype inheritance, where any object can be a
prototype to create new instances.

if you say that 'Base' is a class, then there's no surprise, since
'test' is a class variable, and is shared by all instances, even
instances of subclasses.

if you say that 'Base' is an instance of 'Class', then, it's the
prototype of both A and B, which are classes too, and since they don't
redefine 'test', then they share it.

if you say that it's prototype inheritance, then all four variables
(Class, Base, A and B) are prototypes in a hierarchy, there's no
instances or classes.  then everything that is not redefined is
inherited.



> I believe this happens because all tables are referenced whilst other types
are actually copied

not exactly.  in the language's model, _every_ variable and table
field holds only a reference to a value.  since most values are
inmutable, then there's no way to tell a shared object from a copy, so
it's an implementation detail that some of them are copied.  (not all,
strings are referenced but you don't see the difference).

about being error-prone, i'd say that you should make a choice: either
you want a class/instance model or a prototype model.

in a class/instance, you could separate which fields belong to the
class (shared) and which are a 'template' to be copied to the
instance.

in a prototype model, just recognize that everything in the prototype
is accessible from the subobjects, so you should create any non-shared
data in the creator.  That's how both JavaScript and Python do it, the
'instance' fields aren't declared, just defined in the creator.

--
Javier

Reply | Threaded
Open this post in threaded view
|

Re: inheritance and sub-tables

arioch82
Hi Javier,

thanks for your reply, i have changed the first post to explain things a little bit better and i have also slightly changed the code to make it more clear, if you can have a look at it again i will appreciate it.

What i am trying to do is simulate class inheritance in the way C++ handles it, Class/Base/A/B are all classes definitions used to create instances (through the same New function) and i would expect them to not affect each other; redefining functions i am trying to simulate the "virtual"/"override" behavior you can have in C++

when you say:

> in a class/instance, you could separate which fields belong to the
> class (shared) and which are a 'template' to be copied to the
> instance.

how would i do that? how can i define a table to be "copied over" to the instance?

Thanks again
Reply | Threaded
Open this post in threaded view
|

Re: inheritance and sub-tables

Andrew Starks
On Wed, Feb 26, 2014 at 2:12 PM, arioch82 <[hidden email]> wrote:
> how would i do that? how can i define a table to be "copied over" to the
> instance?
>
> Thanks again

Penlight works this way (not to keep sounding like a commercial for it. :)

https://github.com/stevedonovan/Penlight/blob/master/lua/pl/class.lua

when you set the index metamethod of your object to self, within new,
then you're saying that the class fields are the fall back to your
object. So, if the object has the field assigned, then it never checks
the class. You have the object table inside of the New constructor.


local class = require 'pl'.class

local My_class = class.My_class()


function My_class:method(args) --all objects will use *this* function
(not a just copy of it), if the program accesses the "method" field.
  --do method stuff
  self.objcect_prop = args.value ---etc

end

function My_class:_init(constructor_args) --- you called this "New".
It's init in Penlight
    self.name = constrictor_args.name --only this object will have this.

end

---

In Pen light, the My_class.__call metamethod is a function that calls
this _init function, passing in a new object table as the first
argument. So it's something like this:

__call = function (klass, ...)


    local obj = {}
    --blablabla, features features features

   klass._init(obj, ...) --do the stuff init the init function
   setmetatable(obj, klass)

  return obj

end


It's a little more complicated, because penlight also supports
getters/setters and other fun.

When you do inheritance, you need to decide if you're going to flatten
the objects so that there is only one metatable. That is, the last
inherited Class filters any previous fields (properties or methods).
The other way is to tell your __index method to loop through an
inheritance chain.

Penlight flattens (i believe). This is faster, but there may be a
conceived behavior of OOP that does not work with this.

As Javier said: Lua doesn't have objects. But, you can fake them really well. :)

AND: this is one thing that took me a while to learn....

Start with very little "pop" behavior. Do you really really really
need all the features of some other language that you've used? Lua can
do a crazy amount with very little. Just some wounds from experience.
:)

-Andrew

Reply | Threaded
Open this post in threaded view
|

Re: inheritance and sub-tables

Javier Guerra Giraldez
On Wed, Feb 26, 2014 at 4:21 PM, Andrew Starks <[hidden email]> wrote:
> As Javier said: Lua doesn't have objects. But, you can fake them really well. :)


wowowow, i never said that!

one, Lua does have objects... every value is an object, be it OOP or not.

two, these OOP implementations aren't "fake" in any way, they're just
as real as those on C++, or JavaScript.  just because it's not
pre-built, it's nothing 'lesser', or second-class!


what I said (and repeat) is that Lua doesn't have inheritance, but
it's easy to implement it.  Since the problem at hand was a confusing
implementation, i felt it was important to separate it.

now, i'll add (once again) that inheritance is overrated, and only
really needed for static type languages.  i'm much happier with OOP in
Lua when there's no OOP library on sight, just a simple and concise
writing style  :-)

--
Javier

Reply | Threaded
Open this post in threaded view
|

Re: inheritance and sub-tables

Andrew Starks
On Wed, Feb 26, 2014 at 3:39 PM, Javier Guerra Giraldez
<[hidden email]> wrote:
> wowowow, i never said that!
>
> one, Lua does have objects... every value is an object, be it OOP or not.


My apologies. I was lazy in a way that was material, which means I was wrong.

> two, these OOP implementations aren't "fake" in any way, they're just
> as real as those on C++, or JavaScript.  just because it's not
> pre-built, it's nothing 'lesser', or second-class!

I agree with this. Back in the olden days, people would argue about
whether or not C++ was "truly object-oriented" or not. I was using the
word "fake" in this context. You can get what you need done, even when
you might choose a route that doesn't give you "true" data privacy,
for example. Or you can go all of the way and build up an OOP system
that meets your arbitrary definition of "true"...

Instead of "fake OOP", one might say that you can "ape" any OOP style
that you might care to emulate.


-Andrew

Reply | Threaded
Open this post in threaded view
|

Re: inheritance and sub-tables

Sean Conner
In reply to this post by Javier Guerra Giraldez
It was thus said that the Great Javier Guerra Giraldez once stated:
> On Wed, Feb 26, 2014 at 4:21 PM, Andrew Starks <[hidden email]> wrote:
> > As Javier said: Lua doesn't have objects. But, you can fake them really well. :)
>
> wowowow, i never said that!
>
> one, Lua does have objects... every value is an object, be it OOP or not.

  Well, by that definition, so is C; it's just that it has even less
syntactic sugar than Lua.

  -spc



Reply | Threaded
Open this post in threaded view
|

Re: inheritance and sub-tables

Rena
On Wed, Feb 26, 2014 at 5:14 PM, Sean Conner <[hidden email]> wrote:
It was thus said that the Great Javier Guerra Giraldez once stated:
> On Wed, Feb 26, 2014 at 4:21 PM, Andrew Starks <[hidden email]> wrote:
> > As Javier said: Lua doesn't have objects. But, you can fake them really well. :)
>
> wowowow, i never said that!
>
> one, Lua does have objects... every value is an object, be it OOP or not.

  Well, by that definition, so is C; it's just that it has even less
syntactic sugar than Lua.

  -spc




GTK+ does a pretty decent job of implementing OOP in C. It's not pretty, but it works really well.

--
Sent from my Game Boy.
Reply | Threaded
Open this post in threaded view
|

Re: inheritance and sub-tables

Javier Guerra Giraldez
In reply to this post by Sean Conner
On Wed, Feb 26, 2014 at 5:14 PM, Sean Conner <[hidden email]> wrote:
>> one, Lua does have objects... every value is an object, be it OOP or not.
>
>   Well, by that definition, so is C; it's just that it has even less
> syntactic sugar than Lua.


first, OOP is a design style, not a language feature.  As Rena said,
there are quite complete OOP frameworks in C.  And even without any
extra framework, you can _really_ do OOP in C just by exercising the
theory and defining a style.

second, it's a little hairsplitting and somewhat personal opinion, but
typically I call a value an object when it carries its own type
information.  C primitive values don't, and in structs you can (and
it's sometimes good advice), but not automatically.

--
Javier

Reply | Threaded
Open this post in threaded view
|

Re: inheritance and sub-tables

Andrew Starks
On Wed, Feb 26, 2014 at 4:42 PM, Javier Guerra Giraldez
<[hidden email]> wrote:
> first, OOP is a design style, not a language feature.  As Rena said,
> there are quite complete OOP frameworks in C.  And even without any
> extra framework, you can _really_ do OOP in C just by exercising the
> theory and defining a style.


Well... there are languages that enforce this design style at every
turn. The enforcement of "good OOP design" (good == OOP in this world)
usually means that the language provides facilities for one or a
handful of variations of OOP style. In this case, the "language is
object oriented" and the "language enforces a style that is oriented
towards defining and manipulating objects" can be reasonable taken to
mean the same thing.

It's hard to be as precise as is needed, sometimes. And some times, it
may be okay to not be that precise, if meaning isn't lost? I just said
something heretical, didn't I...

-Andrew

Reply | Threaded
Open this post in threaded view
|

Re: inheritance and sub-tables

Javier Guerra Giraldez
On Wed, Feb 26, 2014 at 5:51 PM, Andrew Starks <[hidden email]> wrote:
> It's hard to be as precise as is needed, sometimes. And some times, it
> may be okay to not be that precise, if meaning isn't lost? I just said
> something heretical, didn't I...


well, we're getting further and further off-topic... we should drop
the hairsplitting :-)

--
Javier

Reply | Threaded
Open this post in threaded view
|

Re: inheritance and sub-tables

Javier Guerra Giraldez
In reply to this post by arioch82
On Wed, Feb 26, 2014 at 3:12 PM, arioch82 <[hidden email]> wrote:
> thanks for your reply, i have changed the first post to explain things a
> little bit better and i have also slightly changed the code to make it more
> clear, if you can have a look at it again i will appreciate it.


modified it?  i read this as a mailing list, so there's no way to
change.  i checked the list archive and couldn't see any change (nor
any 'edit' button)

please, if you want to discuss any changes, post them.

--
Javier

Reply | Threaded
Open this post in threaded view
|

Re: inheritance and sub-tables

steve donovan
In reply to this post by Javier Guerra Giraldez
On Thu, Feb 27, 2014 at 12:42 AM, Javier Guerra Giraldez
<[hidden email]> wrote:
> second, it's a little hairsplitting and somewhat personal opinion, but
> typically I call a value an object when it carries its own type
> information.  C primitive values don't, and in structs you can (and
> it's sometimes good advice), but not automatically.

That feels like a good distinction - C values are (typically) dumb,
pointers point to exactly what you've declared, as you expect them to
be laid out in the struct, modulo padding.

However, instances of a C++ class with virtual methods [1] do carry
type information, even if it's just a VMT.  A virtual method obj->M(a)
is implemented something like the pseudo-code[2]
obj->VMT[M_slot](obj,a) - which is not a million miles away from the
Lua definition. The difference is that 'slots' are indices into a
little array.

[1] "member function" is more correct, but clunky. Some would argue
that the word 'method' already includes the adjective 'virtual'.
[2] Standard does not dictate actually where the VMT is stored; that's
determined by the ABI (see
http://stackoverflow.com/questions/10925115/location-of-virtual-function-table-pointer-in-object)