0
obj = {}

function obj:setName(name)
    print("obj: ", self)
    print("name: ", obj)
end

I create an object and assign a method like above. Now I call it this way:

obj:setName("blabla")

The self identifier then refers to obj. My problem is that the function could also potentially be accessed via

obj.setName("blabla")

In this case, obj won't be passed as an argument and "blabla" will take the place of the self parameter instead of serving the name. That is because the : operator in the function declaration is only a shorthand/sugar syntax for

function obj.setName(self, name)

Can I somehow properly check if self really is the subject/if the function has been run by colon? It cannot be told from argCount nor can I write obj in the function directly because it will be instantiated and the function is refered to from outside the scope where I define it. My only idea is to check if self possesses a member "setName"

function obj:setName(name)
    if ((type(self) ~= "table") or (self.setName == nil)) then
        print("no subject passed")

        return
    end

    print("obj: ", self)
    print("name: ", obj)
end

but that's not clean either.

edit: Doing it like this now:

local function checkMethodCaller()
    local caller = debug.getinfo(2)

    local selfVar, self = debug.getlocal(2, 1)

    assert(self[caller.name] == caller.func, [[try to call function ]]..caller.name..[[ with invalid subject, check for correct operator (use : instead of .)]])
end

function obj:setName(name)
    checkMethodCaller()

    print(self, name)
end
  • You can write it in one line: `assert(self.setName)` to stop executing your script on wrong invocation. – Egor Skriptunoff May 19 '13 at 16:24
  • Got an idea, compare self.setName to obj.setName (the function). If this matches, that has to be the called function. – user2399203 May 19 '13 at 18:04
  • Are you *really* sure you want to do this? This sort of magic could be confusing and error prone in the long run. Perhaps you can change the definition of your `:`-using object to use `.` methods instead or maybe you could just add a flag to tell your processing function whether it should use the `:` or `.` methods? – hugomg May 19 '13 at 19:40
  • @missingno It's a short run. I have written a little lib that shall fulfill a dedicated purpose for other users. They should just not be able to accidentally access the functionality in a false way/it happens to myself as well picking the wrong operator. The method notation is better for readability. An extra flag would be even more error-prone and there are functions with variable parameters. – user2399203 May 19 '13 at 20:46
  • "They should just not be able to accidentally access the functionality in a false way": But how will you be able to be sure that the "object-detecting" heuristic works all the time? Are you really sure that you can't change your interface so that there is only one way to do things? – hugomg May 19 '13 at 21:01

2 Answers2

0

You can assign a metatable to the object and inside the setName method just check whether self's metatable is appropriate:

obj = {}
local objmt = {}

setmetatable(obj, objmt)

function obj:setName(name)
    if getmetatable(self) ~= objmt then
        error("Panic, wrong self!")  -- or handle it more gracefully if you wish
    end
    self.name = name
end

EDIT:

Of course, if someone intentionally replaces or removes your metatable, it will entirely break the function.

W.B.
  • 5,445
  • 19
  • 29
0

Usually documentation trumps type checks in scripting. If you check everything you will eventually see some performance impact. Start by adding good function documentation headers.

That said, the following options come to mind:

Usually just type-testing the self-argument is enough to safeguard against typing errors, as name usually is a string, so if you accidentally type obj.setName("Foo") the type of self is a string and not a table.

-- Test type of argument
function obj:setName(name)
   assert(type(self) == "table");
   -- more code
end

You can of course use the number of arguments too. Note that I used >= 2 instead of == 2, this is useful because if you chain some calls like obj:setName(foo:getInfo()) additional return values would otherwise break execution even though your name argument may be the correct value.

-- Check number of arguments
function obj.setName(...)
   assert(select('#', ...) >= 2);

   local self, name = ...;
   -- more code
end

The following variant goes even a step further, it not only assures that your self is a table and contains the right function, but that self is the very same table. This works because comparing tables does not compare their contents in Lua, but their table ID which is unique.

However, this also requires a closure for every method and object instantiated. It is a bit of an overhead.

-- Compare against object table
function Construct()
   local o = {};

   function o:setName(name)
      assert(self == o);
      -- more code
   end

   return o;
end

A last variant that comes to mind (mainly because I have written some code very similar to it) is keeping track of your objects through a constructor function while using a prototype

local Objects = setmetatable({}, { __mode = "k" });
-- the __mode just makes the GC ignore this table so that your objects can be
-- collected by the GC if not used any more. It would work without the metatable
-- but be a memory leak.
local Prototype = {
   setName = function(self, name)
      assert(IsMyObject(self));
      -- more code
   end
}

function IsMyObject(self)
   return not not Objects[self];
end

function ConstructMyObject()
   local o = {};
   Objects[o] = { __index = Prototype };
   return setmetatable(o, Objects[o]);
end

The obvious benefit is that your methods are not individual closure for each object any more. However, this way you can also easily do other things like making your object immutable, or implement basic inheritance.

dualed
  • 10,262
  • 1
  • 26
  • 29