Reputation: 15670
I'm trying my hand at creating classes in lua. I've written some simple code to set and get a string value, but i keep getting null back as a result.
Here's what my class definition looks like:
appUser = {}
appUser_mt = { __index = appUser }
-- This function creates a new instance of appUser
function appUser:new()
local new_inst = {} -- the new instance
setmetatable( new_inst, appUser_mt)-- all instances share the same metatable.
return new_inst
end
-- Here are some functions (methods) for appUser:
function appUser:setLastName(lname)
appUser._lname = lname
end
function appUser:setFirstName(fname)
appUser._fname = fname
end
function appUser:getLastName()
return appUser._lname
end
function appUser:getFirstName()
return appUser._fname
end
return appUser -- do I need this???
My test code that tries to create 2 users looks like this:
require "user"
local a = appUser:new{setLastName="Doe"}
local b = appUser:new{setLastName="Doe-Doe"}
print(a.getLastName())
print(b.getLastName())
When I run the test script from the commandline, this is what I get:
mydevbox:/usr/share/vsx/lib/testapp# lua testuser.lua
nil
nil
mydevbox:/usr/share/vsx/lib/testapp#
What I've tried so Far:
I've tried changing the way I call the methods from print(a.getLastName()) to print(a:getLastName())
I've changed the variable definitions from:
appUser._lname to _lname
so that my code look like this:
local _lname
function appUser:setLastName(lname)
_lname = lname
end
function appUser:getLastName()
return _lname
end
I'm not sure what I'm doing wrong. Any suggestions would be appreciated. Thanks.
EDIT 1
The following changes have been made to the code to test Etan's answer and mine: 1. I've changed the constructor to accept a parameter as noted in my answer... 2. I've added the reference to "self" in the body of the set/get functions.
-- This function creates a new instance of appUser
appUser = {}
appUser_mt = { __index = appUser }
function appUser:new(new_inst)
new_inst = new_inst or {} -- the new instance
setmetatable( new_inst, appUser_mt)-- all instances share the same metatable.
return new_inst
end
-- Here are some functions (methods) for appUser:
function appUser:setLastName(lname)
self._lname = lname
end
function appUser:setFirstName(fname)
self._fname = fname
end
function appUser:getLastName()
return self._lname
end
function appUser:getFirstName()
return self._fname
end
object A and B in the test script still fail with nil, but C works, because I'm not creating it the same way I instantiate a or b. (Please see my answer)
Edit 2
appUser = {}
appUser_mt = { __index = appUser }
function appUser:new()
return setmetatable( {}, appUser_mt)-- all instances share the same metatable.
end
local function validate_vmail(vmail_id)
local success = true
if type(vmail_id) ~= 'string' then
success = false
end
if not exists_in_database(vmail_id) then
success = false
end
return success
end
function appUser_mt.__index(t, k)
if k == 'lastName' then
return t._lastName
end
return rawget(t, k)
end
function appUser_mt.__newindex(t, k, v)
local success = false
if k == 'lastName' then
k = '_lastName'
v = v:gsub('^.', string.upper) -- Make sure the first letter is uppercase.
success = true
end
if k == 'vmail_id' then
if validate_vmail(v) then
v = v
success = true
else
success = false
end
end
if success then
rawset(t, k, v)
end
end
return appUser
And here's the client that instantiates this class:
local a = appUser:new()
a.lastName = "doe"
print(a.lastName)
local b = appUser:new()
b.lastName = "dylan"
print(b.lastName)
a.vmail_id = 1234
print(a.vmail_id)
The code seems to be working, but I'd like to make sure I understood your comments / answers thus far.
Upvotes: 3
Views: 3755
Reputation: 8000
You're not assigning the result of require "user"
anywhere, so I assume you might have a module
call hidden away somewhere. You need return appUser
if you intend to use it without calls to module
, such as this:
-- mlib.lua
local M = {}
function M.add(a, b) return a + b end
return M
-- script.lua
local m = require'mlib'
print(m.add(2, 2))
You can simplify your constructor function.
function appUser.new()
return setmetatable({}, appUser_mt)
end
I think I understand your intention with the following code, but it's not going to work the way you want it to without a little tweaking. I'll come back to this.
local a = appUser:new{setLastName="Doe"}
local b = appUser:new{setLastName="Doe-Doe"}
Remember that any time you define a function with :
, you're inserting an implicit self
parameter as the first argument. The same goes for calling a function as well.
function t:f() end
function t.f(self) end
t.f = function(self) end
All of these lines are equivalent statements.
t['f'](t)
t.f(t)
t:f()
Setters and getters are not anywhere as needed as the case may be in other languages. There is no concept of private or public interface to tables in Lua, so if all you're doing is assignment and retrieval, your current setup could become a maintenance liability in the future. There is nothing wrong with doing the following.
local class = {}
class.lastName = 'Doe'
In fact, there are metamethod facilities such that you could intercept certain key assignments and perform operations on the values, if you really wanted setters but also wanted them to blend in with other properties. Let me give you an example.
local mt = {}
function mt.__index(t, k)
if k == 'lastName' then
return t._lastName
end
end
function mt.__newindex(t, k, v)
if k == 'lastName' then
k = '_lastName'
v = v:gsub('^.', string.upper) -- Make sure the first letter is uppercase.
end
rawset(t, k, v)
end
local class = setmetatable({}, mt)
class.lastName = 'smith'
print(class.lastName) -- Smith
To break this down a little bit, let's look at the following statement.
local a = appUser:new{setLastName="Doe"}
The {setLastName = "Doe"}
part is simply constructing a table with a key (or property) named setLastName
with the value of "Doe"
, which are both strings. This is then used as the second argument to the new
function of appUser
. appUser
is the first argument, used as the implicit self
parameter mentioned earlier. Remember the :
.
That is actually not necessary at all. It's enough to call and define appUser.new
as a function without a self
, since it doesn't need one. That's all there is to this. new
is not yet special in any way that it would somehow know to call setLastName
with the parameter "Doe"
, but we can make it that way.
With all that said, here's an idea of how you could get your constructors to work the way you want.
function appUser.new(options)
return setmetatable(options or {}, appUser_mt)
end
local a = appUser.new{_lname = 'Doe'}
local b = appUser.new{_lname = 'Doe-Doe'}
However, if you actually wanted to call functions in the constructor table itself, then that requires a little more.
function appUser.new(options)
local o = setmetatable({}, appUser_mt)
for k, v in pairs(options)
-- This will iterate through all the keys in options, using them as
-- function names to call on the class with the values
-- as the second argument.
o[k](o, v)
end
return o
end
local a = appUser.new{setLastName = 'Doe'}
local b = appUser.new{setLastName = 'Doe-Doe'}
Please let me know if this has not yet answered your question as completely as possible.
Upvotes: 3
Reputation: 15670
The way I was instantiating the objects is failing...
require "user"
local a = appUser:new{setLastName="Doe"}
local b = appUser:new{setLastName="Doe-Doe"}
print(a:getLastName())
print(b:getLastName())
local c = appUser:new()
c:setLastName("Dylan")
print(c:getLastName())
The results for object "c" show up correctly...
So I guess the problem is with my constructor. i've tried changing it to:
function appUser:new(new_inst)
new_inst = new_inst or {} -- the new instance
setmetatable( new_inst, appUser_mt)-- all instances share the same metatable.
return new_inst
end
But that hasn't fixed object a or b yet, but I am getting proper results for c
Upvotes: 1
Reputation: 81032
That changed code looks like it should work correctly however it, like the original, has the problem that it will only ever support a single lname or fname for all appuser instances. You need to use the automagic self
variable in your defined functions so that they operate on the passed in instance correctly.
function appUser:setLastName(lname)
self._lname = lname
end
function appUser:getLastName()
return self._lname
end
Also this code is not doing what you think it does (and what you want it to do).
local a = appUser:new{setLastName="Doe"}
local b = appUser:new{setLastName="Doe-Doe"}
It is not calling the setLastName function on your new appUser instance. You are creating a table (the {...}
), giving that table a setLastName
key with the given value Doe
or Doe-Doe
, and then passing that table to the appUser:new function (which then prompty ignores it as it takes no arguments).
Upvotes: 0