Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

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

Comments

  • SimeonSimeon Admin Mod
    edited February 2017 Posts: 4,957

    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?

  • dave1707dave1707 Mod
    Posts: 7,602

    @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.

  • SimeonSimeon Admin Mod
    edited February 2017 Posts: 4,957

    @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.

  • dave1707dave1707 Mod
    Posts: 7,602

    @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
    
  • edited February 2017 Posts: 175

    @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.

  • edited February 2017 Posts: 175

    @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 ;)

    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
    
Sign In or Register to comment.