middleclass: add getter-setter support for properties - oop

I'm trying to add automatic support for property declarations, so that a class gets getters and setters generated automatically for them. I use the middleclass library as a base for classes. I've defined a root class that handles property creation. However, in tests, only an immediate subclass of the root class works properly. Others give me stack overflow error deep inside the middleclass code ([string "local middleclass = {..."]:82: stack overflow).
My code is:
local CBaseObject=class('CObjectBase');
function CBaseObject:initialize()
self._init=true;
end;
function CBaseObject:finalize()
self._init=false;
end;
function CBaseObject:_getter_setter(v)
return v;
end;
function CBaseObject:_gen_prop_cache()
rawset(self,'_properties',rawget(self,'_properties') or {});
end;
function CBaseObject:__index(k)
print('GET',k);
self:_gen_prop_cache();
local prop=self._properties[k];
if prop~=nil
then
local getter=self[prop[2] or '_getter_setter'];
return getter(self,prop[1]);
else return nil;end;
end;
function CBaseObject:__newindex(k,v)
print('ME',self.class.name);
print('SET',k,v);
self:_gen_prop_cache();
local prop=self._properties[k];
if prop==nil and self._init or prop
then
if prop==nil then prop={};self._properties[k]=prop;end;
local vv=prop[1];
if type(v)=='table' and #v<4
then
for i=1,3 do prop[i]=v[i];end;
else
prop[1]=v;
end;
local setter=self[prop[3] or '_getter_setter'];
prop[1]=setter(self,prop[1],vv);
else
rawset(self,k,v);
end;
end;
Test classes:
local cls=CBaseObject:subclass('test');
function cls:initialize()
self.class.super.initialize(self);
self.health={1000,'_gethealth','_sethealth'};
self.ammo=100;
self:finalize();
end;
function cls:_sethealth(value,old)
print('WRITE:',value,old);
if value<0 then return old;else return value;end;
end;
function cls:_gethealth(value)
print('READ:',value);
return value/1000;
end;
local cls2=cls:subclass('test2');
function cls2:initialize()
self.class.super.initialize(self);
self.ammo=200;
self:finalize();
end;
function cls2:_sethealth(value,old)
print('WRITE_OVERRIDEN:',value,old);
return value;
end;
local obj=cls2(); --change this to cls() for working example.
obj.health=100;
obj.health=-100;
print(obj.health,obj._properties.health[1]);
print(obj.ammo,obj._properties.ammo[1]);
I used https://repl.it/languages/lua to run my code. So, the questions is, is what I do even the right approach? Is it possible to add property support in an easier way compatible with the used library? Or should I use another, and what then?
EDIT: after experimenting I found out that the construct self.class.parent.<method>(<...>) is to blame for the error. I replaced all such occurrences by actual parent classes. That was the only problem, it seems, after that the code started to work error-free so far.

Enrique GarcĂ­a Cota (middleclass creator) has enlightened me with what I think is a nice way to implement getters/setters on middleclass.
He suggested to create and use a mixin.
I've done some minor changes while testing/using this mixin recipe. Currently the one I'm using looks like this:
-- properties.lua
local Properties = {}
function Properties:__index(k)
local getter = self.class.__instanceDict["get_" .. k]
if getter ~= nil then
return getter(self)
end
end
function Properties:__newindex(k, v)
local setter = self["set_" .. k]
if setter ~= nil then
setter(self, v)
else
rawset(self, k, v)
end
end
return Properties
You would have to create function get_* and function set_* for your properties (or modify the string pattern above as you wish).
Example:
local class = require('middleclass')
local Properties = require('properties')
local Rect = class('Rect'):include(Properties)
function Rect:initialize(x, y, w, h)
self.x = x
self.y = y
self.w = w
self.h = h
end
function Rect:get_right()
return self.x + self.w
end
function Rect:set_right(right)
self.x = self.x + right - self.right
end
r = Rect:new(10,10, 100, 100)
print(r.right) -- 110
print(r:get_right()) -- 110
r.right = 200
print(r.right) -- 200
print(r.x) -- 100
This way you can use this mixin in whatever classes you want to have properties and simply create the get_* and set_* functions on it.
He also said, however:
I am not a huge fan of getters/setters in Lua. In other languages, I can accept them; for example in ruby, they are integrated in the language's message-passing mechanism.
But in Lua they are extra syntactic sugar, and risk making things more "magical" (unexpected to someone unfamiliar with the code).
Performance note: It's worth mentioning, however, that using __index functions, like the example does, should impact heavily on your code's performance (if compared to __index tables).
Personally, after some time using getters and setters (due to my Python bias), I've decided to write things explicitly and no longer rely on them.
It depends, of course, if performance is critical or not for your code.

Related

What to use as metatable?

Reading http://lua-users.org/wiki/LuaClassesWithMetatable the Vector example uses the following technique for the metatable:
Vector={}
Vector_mt={__index=Vector}
function Vector:new(x,y)
return setmetatable({x=x,y=y},Vector_mt)
end
function Vector:add(v)
return Vector:new(self.x+v.x,self.y+v.y)
end
Supposing we want to use __add to support the + operator, we need to explicitly mention it in the metatable, and also we need to reorder things, so that the metatable and the constructor are mentioned after Vector:add:
Vector={}
function Vector:add(v)
return Vector:new(self.x+v.x,self.y+v.y)
end
Vector_mt={__index=Vector,__add=Vector.add}
function Vector:new(x,y)
return setmetatable({x=x,y=y},Vector_mt)
end
To avoiding having to mention each metamethod in the metatable, I can set Vector itself as the metatable, then I can add __add (plus, obviously, __index) as a method of Vector:
Vector={}
function Vector:new(x,y)
return setmetatable({x=x,y=y},Vector)
end
function Vector:add(v)
return Vector:new(self.x+v.x,self.y+v.y)
end
function Vector:__index(k)
return Vector[k]
end
function Vector:__add(b)
return self:add(b)
end
is the latter not recommended and why?
and also we need to reorder things, so that the metatable and the constructor are mentioned after Vector:add
No. A table is a reference. On adding metatable, it is not deep copied. You can add fields to it later, and it will affect the metatable.
This is over-complicated:
function Vector:__index(k)
return Vector[k]
end
Simply do this:
Vector.__index = Vector
See https://codereview.stackexchange.com/a/253022/230923 for an example of a Lua "class".

How can I define a simple non-method function on wren?

The documentation of the wren scripting language http://wren.io/ explains how to define methods within a class, but I want to define a simple function and not a method. I tried this:
#! /usr/bin/env wren
square(n) {
return n * n
}
System.print(square(3))
and this (omitting the parts other than the attempted function definition:
var square= {|n|
return n * n
}
and this
var square= Fn.new {|n|
return n * n
}
but nothing worked.
Any advice how I could make it work without resorting to method definitions?
I found a solution: Obviously I forgot the invoke the call() method on the function object. This works:
#! /usr/bin/env wren
var square= Fn.new {|n|
return n * n
}
System.print(square.call(3))
However, I find it hard to believe that all user-defined functions have to be called by explicitly invoking a "call" method on them!
The last time I have seen something like that where "call" had to be written explicitly all the time was TI BASIC for the TI-99/4a.
But that was about 35 years ago! ;-)

Can I dynamically call a local function?

I have a module that looks like this. I've simplified it greatly so as not to clutter up my question (my local functions are more complex than this). Here is my code:
decision = {}
function win(win_arg) return win_arg end
function lose(lose_arg) return lose_arg end
local function draw(draw_arg) return draw_arg end
function decision.get_decision(func, arg)
return func(arg)
end
return decision
I am calling my module with the code below.
my = require "my-lua-script"
print(my.get_decision(lose, "I lose."))
print(my.get_decision(win, "I win."))
I want 'get_decision' to be a public method. I want win, lose and draw to be private, but I want to call them dynamically from get_decision. If I understand correctly, win and lose are in the global namespace right now? If I put a local in front of these two methods (like draw) then my code doesn't work.
Is there a way to accomplish what I am after?
Of course.
my-script.lua
-- This is the local side of the module.
local outcomes = {}
function outcomes.win(arg) return arg end
function outcomes.lose(arg) return arg end
function outcomes.draw(arg) return arg end
-- This is the exposed side of the module.
local M = {}
function M.get_decision(outcome, arg)
return outcomes[outcome](arg)
end
return M
main.lua
local my = require'my-script'
print(my.get_decision('win', 'I win.'))
print(my.get_decision('lose', 'I lose.'))
You simply use a string to indicate which function you'd like to access, and use that string to index a table of functions from within get_decision against outcomes. This will keep the functions hidden behind get_decision.

Creating Lua objects during callback issue

I making simple project on Delphi + Lua to use Delphi visual components through RTTI. Main implementation idea based on luna.h. I like Blizzard's idea of using templates stored in xml (when you can put several objects on TPanel for example and use it as template), so I almost implemented that also.
The problem is: everything works fine until I creating objects through callback from Lua made with almost same code as luna.h Inject code. If this object have "inherits" xml attribute callback function calls creation of objects stored in xml, creating another object through yet another injection. And I receive errors leading to lua51.dll.
Maybe that problem caused by using stack by both callback and Inject while callback function not returned result yet. So can I use stack and create objects through callback? If not - is there any workarounds to implement it? Blizzard really made it somehow.
More details:
1. Application registering Lua objects (for example TPanel and many others) through
cn := T.ClassName;
f := #StaticOnCreate;
lua_pushlightuserdata(L, self); // put offset to self into lightuserdata
lua_pushcclosure(L, f, 1);
lua_setglobal(L, PAnsiChar(UTF8Encode(cn))); // T() in lua will make a new instance.
difference from luna.h - application also stored pointer to object (Delphi objects are pointers) to avoid generics (c templates)
2. Now Lua have TPanel in global table
3. By using
p = TPanel("somename")
in script, Lua calls application's StaticOnCreate.
4. StaticOnCreate extracts object and calls class's function
o := TLuaClassTemplate(lua_topointer(L, lua_upvalueindex(1)));
result := o.OnCreate(L);
5. OnCreate function extracts params like name etc and creates exact visual object of type TPanel and by using same code as luna.h for inject function
lua_newtable(FL); // create a new table for the class object ('self')
obj_index := lua_gettop(FL);
lua_pushnumber(FL, 0);
a := lua_newuserdata(FL, SizeOf(pointer)); // store a ptr to the ptr
a^ := obj; // set the ptr to the ptr to point to the ptr... >.>
luaL_newmetatable(FL, PAnsiChar(UTF8Encode(cn))); // get (or create) the classname_metatable
lua_pushstring(FL, PAnsiChar(UTF8Encode('__gc')));
lua_pushlightuserdata(FL, self); // put offset to self into lightuserdata
lua_pushcclosure(FL, #StaticGc_T, 1);
lua_settable(FL, -3);
lua_setmetatable(FL, -2); // userdata.metatable = classname_metatable
lua_settable(FL, obj_index); // self[0] = obj;
f := #StaticThunk;
// register the functions
for i := 0 to length(ClassApiArray) - 1 do begin
lua_pushstring(FL, PAnsiChar(UTF8Encode(ClassApiArray[i].name)));
lua_pushlightuserdata(FL, self); // put offset to self into lightuserdata
lua_pushnumber(FL, i); // let the thunk know which method we mean
lua_pushcclosure(FL, f, 2);
lua_settable(FL, obj_index); // self["function"] = thunk("function")
end;
lua_pushvalue(FL, -1); // dup object on stack top
rec.ref := luaL_ref(FL, LUA_REGISTRYINDEX); // store object as ref
...
result := 1;
returns Lua object through stack, so now p from p = TPanel("somename") is object isntance.
But issue if I trying to implement xml templates and step 3
p = TPanel("somename", "xmltemplatenodename")
and during execution step 5 in OnCreate if I see there is inherits = xmltemplatenodename. So during step 5, before inject, application searching for exact xmltemplatenodename - if found creating more objects with inject for each object. And only after that continues execution of step 5 with own inject. Here I have errors leading to lua51.dll after creating 2-3 objects from xmltemplatenodename. But if application uses same xml as source and creates same xmltemplatenodename objects outside of OnCreate - there is no errors.
Inject for xml object slightly different, isntead of leaving object in stack it registers it by object name
lua_setglobal(FL, PAnsiChar(objName8));

Following calls to static methods with indexing when importing classes

I have a class file myClass.m in a package folder +myPack that's on the path. A simple example of the class file is:
classdef myClass
properties
prop
end
methods
function obj = myClass(x)
obj.prop = x;
end
end
end
Now if I directly call the method and access the property using the full package name, i.e.:
x = myPack.myClass(2).prop;
returns x = 2 correctly. Now, if I try the same by importing this class (and not use the package name):
import myPack.myClass
y = myClass(2).prop
it gives me the following error:
Static method or constructor invocations cannot be indexed.
Do not follow the call to the static method or constructor with
any additional indexing or dot references.
Why does this work in the first case and not the second? As far as I understood, importing a class mainly allowed one to use the class name without the long package name (among other considerations). What is the difference in these two that causes this error and how can I work around it?
Here is some more weird for you: the behavior is different if you are running in the command window, from a script, or from a function!
1) command prompt (1st: ok, 2nd: error)
This is what you've already shown
>> x = myPack.myClass(2).prop
x =
2
>> import myPack.myClass; y = myClass(2).prop
Static method or constructor invocations cannot be indexed.
Do not follow the call to the static method or constructor with
any additional indexing or dot references.
2) Script (1st: error, 2nd: error)
testMyClassScript.m
x = myPack.myClass(2).prop
import myPack.myClass; y = myClass(2).prop
and
>> testMyClassScript
Static method or constructor invocations cannot be indexed.
Do not follow the call to the static method or constructor with
any additional indexing or dot references.
Error in testMyClassScript (line 1)
x = myPack.myClass(2).prop
(the second line would also throw the same error)
3) Function (1st: ok, 2nd: ok)
testMyClassFunction.m
function testMyClassFunction()
x = myPack.myClass(2).prop
import myPack.myClass; y = myClass(2).prop
end
and
>> testMyClassFunction
x =
2
y =
2
I would definitely call that a bug :) The expected behavior is to give an error in all cases.