Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

class.properties to handle _self #48

Open
andrewstarks opened this issue Feb 14, 2013 · 7 comments
Open

class.properties to handle _self #48

andrewstarks opened this issue Feb 14, 2013 · 7 comments

Comments

@andrewstarks
Copy link
Contributor

Sometimes, I want a class that is handling array data and I want to check input that is entered directly onto the class.

local my_obj = MyClass()
my_obj[1] = "foo"
--the class does some checking or other work, 
--in addition to adding "foo". Such as...
print(my_obj[1])
--> FOO

class.properties works well for this, IF you are working on a member of the object, not directly on indexes of the object, itself.

The following is a patch of the class.properties._class_init function (BTW: _class_init needs documentation! :))

class.properties = class()

function class.properties._class_init(klass, _self)
    local _self = type(_self) == "table" and _self or false

    klass.__index = function(t,key)
        -- normal class lookup!
        local v = klass[key]
        if v then return v end
        -- is it a getter?
        v = rawget(klass,'get_'..key)
        if v then
            return v(t)
        end
        --are we getting/setting self?
        if _self then
            --provide easy access to the proxied table
            --we're not trying for total security.
            --this way set_methods can use self._self for direct access.
            if key == "_self" then 
               return _self 
             --if there is a self getter, then use it.
            elseif klass.get_self then
                return klass.get_self(t, key)
            elseif _self[key] then
                 return _self[key]
            else
                return rawget(_self, '_'..key)
            end
        else
        -- is it a field? 
            return rawget(t, '_'..key)
        end
    end
    klass.__newindex = function (t,key,value)
        -- if there's a setter, use that, otherwise directly set table
        local p = 'set_'..key
        local setter = klass[p]
        if setter then
            setter(t,value)
        elseif _self and klass.set_self then
            klass.set_self(t, key, value)
         else
             rawset(t,key,value)
         end
     end
 end

The additional overhead is not high. Two extra if``s during operation, provided_self` is not used. The code is not much longer, if you remove my over-commenting.

In operation, the get_self and set_self work as expected. No attempt to block access to _self is made, except that it is hidden as an upvalue to the metatable, so that it does not appear in ipairs or pairs operations. Therefore, class construction is made simple by referencing self._self as needed, or simply not providing a set_self method.

require'pl.import_into'(_ENV)
A = class.A(class.properties, {})
function A:set_self(k, v)
    if type(v) ~= "string" then error("I like strings",3) end
    self._self[k] = v
end
function A:get_self(k)
    return self._self[k]:upper()
end

a = A()
a[1] = "hello"
print(a[1])
print(a._self[1])
a[2] = 12

--> HELLO
--> hello
--> lua:./class_self.lua:16: I like strings

Things to consider:

  • pl.pretty.write doesn't pick up nested classes written this way, mostly because it clobbers the __tostring metamethod (which I think it should not do).
  • One might consider providing simple __len, __tostring, __ipairs, __pairs metamethods, only to make it automatic, if the class creator doesn't want to explicitly take care of this. Perhaps the documentation could just simply point this out, in the name of not doing too much for them.
  • Not sure how this would effect class inheritance, where this was a super of something else....
@stevedonovan
Copy link
Contributor

Interesting! Let me give it a think.

BTW, the stripped-down version of class() in Microlight (see ml.class near the end of ml.lua) has provision for the constructor to return a new self.

    setmetatable(klass,{
        __call = function(klass,...)
            local obj = setmetatable({},klass)
            if rawget(klass,'_init') then
                klass.super = base_ctor
                local res = klass._init(obj,...) -- call our constructor
                if res then -- which can return a new self..
                    obj = setmetatable(res,klass)
                end
            elseif base_ctor then -- call base ctor automatically
                base_ctor(obj,...)
            end
            return obj
        end
    })

(It occurs to me that if that new self was nil, and _init was allowed to return two values, then we would get the Lua-friendly constructor fail that we started discussing.)

@andrewstarks
Copy link
Contributor Author

I saw that too, but I got stuck thinking that there were perhaps 3 entry
points for object creation. I can't remember why, but I think it had
something to do with whether or not there was a base constructor, etc. If
there is only this one, then that seems to make things pretty straight
forward....

On Fri, Feb 15, 2013 at 12:15 AM, Steve J Donovan
notifications@github.comwrote:

Interesting! Let me give it a think.

BTW, the stripped-down version of class() in Microlight (see ml.class near
the end of ml.lua) has provision for the constructor to return a *new self
*.

setmetatable(klass,{
    __call = function(klass,...)
        local obj = setmetatable({},klass)
        if rawget(klass,'_init') then
            klass.super = base_ctor
            local res = klass._init(obj,...) -- call our constructor
            if res then -- which can return a new self..
                obj = setmetatable(res,klass)
            end
        elseif base_ctor then -- call base ctor automatically
            base_ctor(obj,...)
        end
        return obj
    end
})

(It occurs to me that if that new self was nil, and _init was allowed to
return two values, then we would get the Lua-friendly constructor fail that
we started discussing.)


Reply to this email directly or view it on GitHubhttps://github.com//issues/48#issuecomment-13594027.

@andrewstarks
Copy link
Contributor Author

Hey Steve,

Did you get my attachments/emails for the changes I made to PL, so that it
could autoload into a provided table? I'm not sure that github lets you do
attachments.

second,

I applied the necessary patch to PL to make classes fail upon receiving
"nil, err". It needed patching in two spots:

Near the end of call_ctor:

local res, err = c._init(obj,...) --<change starts here.
if not res and err then -- lua for we didn't succeed so fail all nice

like.
return nil, err
end-->Change ends here.

and inside mt.__call

    if rawget(c,'_init') then -- explicit constructor
        local res, err = call_ctor(c,obj,...)  --< change starts here
        if not res and err then -- lua for we didn't succeed so fail

all nice like.
return nil, err
end --<change ends here.

This seems to work well for me.

-Andrew
On Fri, Feb 15, 2013 at 7:48 AM, Andrew Starks andrew.starks@trms.comwrote:

I saw that too, but I got stuck thinking that there were perhaps 3 entry
points for object creation. I can't remember why, but I think it had
something to do with whether or not there was a base constructor, etc. If
there is only this one, then that seems to make things pretty straight
forward....

On Fri, Feb 15, 2013 at 12:15 AM, Steve J Donovan <
notifications@github.com> wrote:

Interesting! Let me give it a think.

BTW, the stripped-down version of class() in Microlight (see ml.class
near the end of ml.lua) has provision for the constructor to return a new
self
.

setmetatable(klass,{
    __call = function(klass,...)
        local obj = setmetatable({},klass)
        if rawget(klass,'_init') then
            klass.super = base_ctor
            local res = klass._init(obj,...) -- call our constructor
            if res then -- which can return a new self..
                obj = setmetatable(res,klass)
            end
        elseif base_ctor then -- call base ctor automatically
            base_ctor(obj,...)
        end
        return obj
    end
})

(It occurs to me that if that new self was nil, and _init was allowed to
return two values, then we would get the Lua-friendly constructor fail that
we started discussing.)


Reply to this email directly or view it on GitHubhttps://github.com//issues/48#issuecomment-13594027.

@stevedonovan
Copy link
Contributor

Nope, didn't arrive here - you used the gmail address?

@andrewstarks
Copy link
Contributor Author

Nope. I don't think I have that... unless it's on the lua list.

On Wed, Feb 20, 2013 at 4:39 AM, Steve J Donovan
notifications@github.comwrote:

Nope, didn't arrive here - you used the gmail address?


Reply to this email directly or view it on GitHubhttps://github.com//issues/48#issuecomment-13825558.

@stevedonovan
Copy link
Contributor

OK, new feature is that a class may define _create and it can return a new self. I'm using it now for pl.List since I wanted to be able to mark an existing table as a List.

@alerque
Copy link
Member

alerque commented Sep 27, 2020

Hey @andrewstarks do you still use pl.class? Do you have any idea off the top of your head how relevant this issue thread still is or what the outcome should be now?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

3 participants