Jump to content

Basic LUA Question: Objects


Recommended Posts

I'm currently in the process of simultaneously re-learning some basic computer science concepts and learning LUA. I was just wondering if anyone wouldn't mind clearing up a concept that I still can't wrap my head around no matter how much I re-read what this textbook says.

I always learned that an object was "an instance of a class"; could someone clarify what that physically means? From what I vaguely remember from Java many years ago, objects were basically variables except you can use functions exclusive to the class of the object (methods) on the object. This textbook so far makes it sound like an object is much more than just a variable with extra characteristics.

Finally, regarding LUA, why is it necessary to pass the object itself into the function? For example:

function Account.withdraw (v)
      Account.balance = Account.balance - v
    end

This is bad.

 

 function Account.withdraw (self, v)
      self.balance = self.balance - v
    end

This is good. Why?

Link to comment
Share on other sites

3 hours ago, Rinkusan said:

I always learned that an object was "an instance of a class"; could someone clarify what that physically means? From what I vaguely remember from Java many years ago, objects were basically variables except you can use functions exclusive to the class of the object (methods) on the object. This textbook so far makes it sound like an object is much more than just a variable with extra characteristics.

Finally, regarding LUA, why is it necessary to pass the object itself into the function? For example:


function Account.withdraw (v)
    Account.balance = Account.balance - v
end

This is bad.


function Account.withdraw (self, v)
    self.balance = self.balance - v
end

This is good. Why?

A class is a definition, and an object is an instance of the definition.  For example, an apple class would contain the basic information that it's a food item, perishable, etc.  While an object of it would contain information specific to that instance such as its exact perishable progression, how big it is, etc.  One apple's size doesn't affect the size of the other when it's changed.

Inherited functions and variables from the class (methods and member variables) should exist on every object instance at the start.  With LUA you're free to mangle the object however you wish to the point that the object no longer follows its root definition.

 

For the code bits if you want an account be an object, then the object created should have its own balance to deal with.

In the first blob 'Account' is being used to store the balance, and as such there's no two instances of Account.

The second one uses the 'self' variable to denote that there are objects in play and so they all have their own 'balance' to keep track of.

 

local DeltaBalance = function(self, delta)
    self.balance = self.balance + delta
end
local PrintDetails = function(self)
    print(string.format("%s's balance: $%.2f", self.ownername, self.balance))
end

CreateAccount = function(ownername, startingbalance)
    local acc = {}
    acc.ownername = ownername
    acc.balance = startingbalance
    acc.DeltaBalance = DeltaBalance
    acc.PrintDetails = PrintDetails
    return acc
end

a = CreateAccount("Bob", 200)
b = CreateAccount("Joe", -10)

a:PrintDetails()
b:PrintDetails()

a:DeltaBalance(-101)
b:DeltaBalance(50)

a:PrintDetails()
b:PrintDetails()

Outputs:

Bob's balance: $200.00
Joe's balance: $-10.00
Bob's balance: $99.00
Joe's balance: $40.00

Note that each account is its own "object" and they both inherit the shared functions DeltaBalance and PrintDetails, while each both have the variables ownername and balance.

The account class denotes these, but you're free to change them on a per-object basis or add things to it.

Adding two classes together into one bigger class and obfuscating away the details is a thing you can also do to divide and conquer complex problems.

 

This isn't using metatables to accomplish similar goals to do so, but with metatables it can ease the burden of creating classes and do some things you can't do without such as overloading and proxy table work.

Edited by CarlZalph
it's -> its
Link to comment
Share on other sites

22 minutes ago, CarlZalph said:

A class is a definition, and an object is an instance of the definition.  For example, an apple class would contain the basic information that it's a food item, perishable, etc.  While an object of it would contain information specific to that instance such as its exact perishable progression, how big it is, etc.  One apple's size doesn't affect the size of the other when it's changed.

Inherited functions and variables from the class (methods and member variables) should exist on every object instance at the start.  With LUA you're free to mangle the object however you wish to the point that the object no longer follows its root definition.

 

For the code bits if you want an account be an object, then the object created should have its own balance to deal with.

In the first blob 'Account' is being used to store the balance, and as such there's no two instances of Account.

The second one uses the 'self' variable to denote that there are objects in play and so they all have their own 'balance' to keep track of.

 


local DeltaBalance = function(self, delta)
    self.balance = self.balance + delta
end
local PrintDetails = function(self)
    print(string.format("%s's balance: $%.2f", self.ownername, self.balance))
end

CreateAccount = function(ownername, startingbalance)
    local acc = {}
    acc.ownername = ownername
    acc.balance = startingbalance
    acc.DeltaBalance = DeltaBalance
    acc.PrintDetails = PrintDetails
    return acc
end

a = CreateAccount("Bob", 200)
b = CreateAccount("Joe", -10)

a:PrintDetails()
b:PrintDetails()

a:DeltaBalance(-101)
b:DeltaBalance(50)

a:PrintDetails()
b:PrintDetails()

Outputs:


Bob's balance: $200.00
Joe's balance: $-10.00
Bob's balance: $99.00
Joe's balance: $40.00

Note that each account is its own "object" and they both inherit the shared functions DeltaBalance and PrintDetails, while each both have the variables ownername and balance.

The account class denotes these, but you're free to change them on a per-object basis or add things to it.

Adding two classes together into one bigger class and obfuscating away the details is a thing you can also do to divide and conquer complex problems.

 

This isn't using metatables to accomplish similar goals to do so, but with metatables it can ease the burden of creating classes and do some things you can't do without such as overloading and proxy table work.

Thanks for some of the clarification.

That being said, I still don't understand the fundamental logic of passing the object itself as a parameter into the method. In Java, whenever I made an Apple object (like for example:Apple applefruit = new Apple ("Red"); ) and called a method like applefruit.getColor();, I never had to pass the applefruit object as a parameter for the getColor method.

Basically, my confusion right now is why applefruit.getColor() doesn't work in LUA but works in Java while applefruit.getColor(applefruit) does work in LUA but not in Java. 


Another thing though; how is your CreateAccount function able to access DeltaBalance? DeltaBalance is in itself a separate function; not only does it have 2 parameters, but it's in an entirely separate chunk. Basically, I don't understand how the DeltaBalance and PrintDetails lines within your CreateAccount function work because to me, it looks like you're assigning a key within the acc table called "DeltaBalance" with the value of a variable called DeltaBalance that doesn't exist. 

Link to comment
Share on other sites

1 minute ago, Rinkusan said:

That being said, I still don't understand the fundamental logic of passing the object itself as a parameter into the method. In Java, whenever I made an Apple object (like for example:Apple applefruit = new Apple ("Red"); ) and called a method like applefruit.getColor();, I never had to pass the applefruit object as a parameter for the getColor method.

Basically, my confusion right now is why applefruit.getColor() doesn't work in LUA but works in Java while applefruit.getColor(applefruit) does work in LUA but not in Java. 


Another thing though; how is your CreateAccount function able to access DeltaBalance? DeltaBalance is in itself a separate function; not only does it have 2 parameters, but it's in an entirely separate chunk. Basically, I don't understand how the DeltaBalance and PrintDetails lines within your CreateAccount function work because to me, it looks like you're assigning a key within the acc table called "DeltaBalance" with the value of a variable called DeltaBalance that doesn't exist. 

Other languages like hide the 'self' variable.  You can use it in the method freely for most of them, but it's not explicitly noted, and sometimes it's referred to as 'this'.  In LUA it doesn't hide it.  Reason for the Java thing is that it's already passing it for you.

See: https://docs.oracle.com/javase/tutorial/java/javaOO/thiskey.html

Specifically:

public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

In LUA, this would be written as:

Point = function(this, x, y)
        this.x = x
        this.y = y
end

 

You may like the syntactic sugar of LUA's obj:func() as it is equal to doing obj.func(obj).  The colon passes the item on the left as the first argument on the right.

 

Since the function is declared above it, and the code is being ran all as 'one file', then the function is still in the scope of the file.  Thus subsequent functions can refer to it and still be valid.

And you're right, I'm assigning a key to acc table to point to the values- something to note is that everything in LUA is a variable (functions, values, tables, etc).  So when I'm assigning the function to the key of the table it's storing the function as its value.

 

Thus when the CreateAccount function returns the table, the table will have a key called 'DeltaBalance' and its value is a function.

a.DeltaBalance(a, 100) == a:DeltaBalance(100)

 

Note that since I'm declaring the functions above and using them for all tables created in CreateAccount each object instance will have the same function.

a.DeltaBalance == b.DeltaBalance

This is sort of like using the __index metatable to call on a table of functions to use, but more explicit in the code to only use these and not everything from the table.

Link to comment
Share on other sites

2 hours ago, CarlZalph said:

Other languages like hide the 'self' variable.  You can use it in the method freely for most of them, but it's not explicitly noted, and sometimes it's referred to as 'this'.  In LUA it doesn't hide it.  Reason for the Java thing is that it's already passing it for you.

See: https://docs.oracle.com/javase/tutorial/java/javaOO/thiskey.html

Specifically:


public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

In LUA, this would be written as:


Point = function(this, x, y)
        this.x = x
        this.y = y
end

 

You may like the syntactic sugar of LUA's obj:func() as it is equal to doing obj.func(obj).  The colon passes the item on the left as the first argument on the right.

 

Since the function is declared above it, and the code is being ran all as 'one file', then the function is still in the scope of the file.  Thus subsequent functions can refer to it and still be valid.

And you're right, I'm assigning a key to acc table to point to the values- something to note is that everything in LUA is a variable (functions, values, tables, etc).  So when I'm assigning the function to the key of the table it's storing the function as its value.

 

Thus when the CreateAccount function returns the table, the table will have a key called 'DeltaBalance' and its value is a function.

a.DeltaBalance(a, 100) == a:DeltaBalance(100)

 

Note that since I'm declaring the functions above and using them for all tables created in CreateAccount each object instance will have the same function.

a.DeltaBalance == b.DeltaBalance

This is sort of like using the __index metatable to call on a table of functions to use, but more explicit in the code to only use these and not everything from the table.

Oh I see. So are you storing the functions into the table that CreateAccount returns because you want to make the 2 functions DeltaBalance and PrintDetails exclusive to CreateAccount objects?

With that being asked, what happens if you remove the 2 lines that add the functions to the table? Are CreateAccount objects not able to use the DeltaBalance and PrintDetails functions?

Edited by Rinkusan
Link to comment
Share on other sites

1 hour ago, Rinkusan said:

Oh I see. So are you storing the functions into the table that CreateAccount returns because you want to make the 2 functions DeltaBalance and PrintDetails exclusive to CreateAccount objects?

With that being asked, what happens if you remove the 2 lines that add the functions to the table? Are CreateAccount objects not able to use the DeltaBalance and PrintDetails functions?

Yeah that's the idea- if this is its own file that's ran, then the local functions aren't directly exposed outside of the file's scope; it would be possible to re-use the function by getting an object from CreateAccount and then using the function reference from the object.

 

And yeah, if there's no reference to the functions then once the file is ran they're no longer in scope and the garbage collector will see it some time and take note that there are no references anywhere which leads it to freeing up the memory used for the function code.

Using a metatable you can auto-magically point any object created by CreateAccount to share all functions and such at once, but the method here is more direct and for your informational purposes in seeing how 'objects' in LUA can be created.

Just note that anything in LUA you create, some bit of code elsewhere can come along and muck it up unless you lock it down tight with some metatable tomfoolery and scope magic.  So in general 'objects', as they are, are always mutable from their class derivative.  Classes are more for your own code organization than enforcement.

Link to comment
Share on other sites

Alright gotcha. Thanks again for the help! It makes a lot more sense now.

Another question though, what's the difference between
-defining "function CreateAccount.DeltaBalance(self,delta)" versus 
-local DeltaBalance = function (self,delta)?

Link to comment
Share on other sites

2 hours ago, Rinkusan said:

Alright gotcha. Thanks again for the help! It makes a lot more sense now.

Another question though, what's the difference between
-defining "function CreateAccount.DeltaBalance(self,delta)" versus 
-local DeltaBalance = function (self,delta)?

no diff in Lua

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
  • Create New...