Class Initialisation Returning nil

Greetings,

I have tried different variations of searching to find the answer to this, however I must be missing something in my Google-Fu.

Is it possible to halt the creation of a class based on incorrect (or missing) parameters?

For example

SomeClass = class()

function SomeClass:init(p)
    if type(p) == table then
        -- continue with class creation
    else
        return nil    -- this class SHOULD NOT be instantiated
    end
end

Thanks in advance,
D

That’s interesting, similar to a failable initializer in Swift.

I can’t see a great way to replicate that behaviour in Codea. You could kind of “destroy” the class instance by replacing its metatable with an anonymous class if the initializer fails, then the is_a method will return false.

Here’s an example:

SomeClass = class()

function SomeClass:init(shouldFail)
    if shouldFail then 
        setmetatable(self, class())
    else 
        -- do normal initialization
    end
end

Then when you instantiate the class you can check:

local valid = SomeClass(false)

print(valid:is_a(SomeClass)) -- should print true

local invalid = SomeClass(true)

print(invalid:is_a(SomeClass)) -- should print false

(In this case the parameter shouldFail should be replaced by whatever failure condition you want to use.)

@Simeon: I was hoping to be able to simply check for nil after I attempted to instantiate the class.

Python has a “hidden” new() method for all classes, does LUA have something similar? Or is init() the only constructor available?

@fly.ing.fox You could set one of the class variables to false. Then see if the instance of that variable is true or false and then just set the class instance to nil. I can’t try any code to see if that actually works.

@fly.ing.fox the class system is just written in Lua, so it could be modified to add the behaviour you’re after.

Here’s a failableClass hack you could write that replaces the class function and creates a class which can return nil in its init method to fail. It has to return self to NOT fail, however.

function failableClass(base)
    local c = class(base)
    local mt = getmetatable(c)

    mt.__call = function(class_tbl, ...)
        local obj = {}
        setmetatable(obj, c)

        local instance = nil

        if class_tbl.init then 
            instance = class_tbl.init(obj, ...)
        end

        return instance
    end

    return c
end

(Edit: the above does support inheritance, but init must be implemented by your subclasses. In the default class function for Codea init is optional, and the base class will be called if the subclass does not declare an initializer.)

Then using it:

SomeClass = failableClass()

SomeClass:init(shouldFail)
    if shouldFail then
        return nil
    end

    -- Important!
    return self
end

And instantiating:

local some = SomeClass(true)

print(some) -- should print nil

some = SomeClass(false)

print(some:is_a(SomeClass)) -- should print true

@Simeon: okay great, thanks for that. Will see if it works in some code I am working on.

The funny thing is that it is my code so I probably will not ever supply the wrong parameters, however if I think it is share-worthy then perhaps I should make it robust.

@fly.ing.fox Here’s the code for what I mentioned above. You can use a seperate variable or use one that gets set when the class is actually created.

function setup()
    -- no table passed, sets a1 to nil
    a1=SomeClass()
    if a1.notCreated then
        a1=nil
    end
    print(a1)
    
    -- table passed, don't set a1 to nil
    a1=SomeClass({"123"})
    if a1.notCreated then
        a1=nil
    end
    print(a1)
end

SomeClass = class()

function SomeClass:init(p)
    if type(p) == "table" then
        -- continue with class creation
    else
        self.notCreated=true
    end
end

@fly.ing.fox Instead of creating your own mechanism, you could also use Lua’s built in error handling functions xpcall or pcall in combination with assert to get the behaviour that you are looking for.

Aside from not having a custom error handling mechanism, there are also a couple of extra benefits to doing it this way:

  • You get an opportunity to handle the error.
  • You can return an error message.
  • You can collect more detailed information such as a stack trace with debug.traceback() in the error handling function. (Generally you want to use xpcall to be able to do this. See Lua Pil 8.5 below for more info.)

You can find out more about xpcall, pcall and assert from:
https://www.lua.org/pil/8.3.html
https://www.lua.org/pil/8.5.html

-- A simple class which causes an error with assert when passed true.
-- assert(condition,message) throws an error with the specified meassage when condition is false
TestClass = class()
function TestClass:init(error)
    assert(error == false, "Invalid parameter")
end

function setup()
    
    -- no error
    xpcall(function() instance1 = TestClass(false) end, 
           function(error) print(error, debug.traceback()) end)
    -- error
    xpcall(function() instance2 = TestClass(true) end, 
           function(error) print(error, debug.traceback()) end)
    
    -- valid instance
    print(instance1)
    
    -- nil
    print(instance2)
end

@XanDDemoX: yes I could do that, however I am trying to emulate the concept from languages like PHP whereby you try to instantiate a class, and if it fails for whatever reason it returns a nil value that you test for on creation.

SomeClass = TheClass(bad)
if (SomeClass == nil) then
    -- halt or handle
end

In addition you could use it like

if (SomeClass = TheClass(bad)) then
    -- this is all good
end

Anyway, I will be testing out the suggestions and see how I go.

@fly.ing.fox Here’s an example of a function that does what you describe. It won’t work for your second example though because its not valid lua syntax :wink:

However you may find that you don’t actually need it because xpcall halts and calls the error callback as soon as it encounters an error anyway. It’s similar to “try” and “catch” in PHP and other languages.

function setup()
    local instance = new(TestClass,false)
    if instance then
        print("success")
    end
    
    instance = new(TestClass,true)
    if not instance then
        print("error")
    end
end

function new(classTable,...)
    local args = {...}
    local result, instance = xpcall(function() return classTable(unpack(args)) end,function(error) end)
    return instance
end

Edit: It can also be written this way to avoid the unpack because Codea supports Lua 5.3.

function new(classTable,...)
    local result, instance = xpcall(function(...) return classTable(...) end,function(error) end,...)
    return instance
end