lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


I have an object model that I was hacking up. I decided to implement a
penlight-like 'sorta private' getter/setter model. Where setting and
getting can be independently controlled by prefixing fields with a
single `_` and methods are defined by prefixing fields with `get_` or
`set_`.

Unlike penlight (which is excellent), mine returns `_*` fields. In
doing so it makes fields remain public, which means that to make them
private/immutable/calculated, you need to make the getter/setter
return `nil` or `error` or whatever. This is what *I* usually want
(example: give them the plain value if they ask, but error if they try
to set it), so I did it this way to avoid writing lots of functions
that simply return the value.

My first attempt (which does not work and can you spot why?):
```
function class:__index(index)
    if type(index) == 'string' then

        return rawget(self, '_' .. index)
            or class['get_' .. index] and class['get_' .. index](self)
            or class[index]
    end
    return class[index]
end
```

If you're new to Lua, and is not already clear, this should help you
spot the bug:

```
function class:__index(index)

    local value = class[index]
    if value ~= nil then
        return value
    end
    value = rawget(self, '_' .. index)
    if value ~= nil then
        return value
    end
    return class['get_' .. index] and class['get_' .. index](self) or nil
end
```
There are less tedious ways to write this, but those involve calling
`rawget` twice on
match; once for `~= nil` and again for the value.

As a side interest, I would be happy to read alternate implementation
ideas for the above. I know that there are small optimizations that
could be done, etc. I don't like the general style of the above
solution, which led to some exploring.

I stumbled upon this model, which I had not observed before, although
it no doubt has been done before (if I am not missing some fatal
flaw):

```
class.index_methods = {
    function(self, index)
        return class[index]
    end,
    function(self, index)
        return rawget(self, '_' .. index)
    end,
    function(self, index)
        return class['get_' .. index] and class['get_' .. index](self)
    end
}

function class:__index(index)
    for i, method in pairs(class.index_methods) do
        local value = method(self, index)
        if value ~= nil then
            return value
        end
    end
    return nil
end
```
Although for this example, this implementation wasteful and over done,
I like this model for more complex cases. To me, it seems clean and
extensible. I have not benchmarked it, but I will assume that it is
also relatively slow, when compared to simpler designs, especially.
However, if multiple fallbacks are a requirement, then this might be
as (in)efficient as any other way. I like it over my other attempts,
so far.

--Andrew
*No proposals were harmed (or contemplated) during the making of this post. :)
**Also, the above code was not tested, after modifying it for clarity.