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