Using environment for OO

classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|

Using environment for OO

Martin C. Martin-2
Hi,

A standard approach to OO in Lua is to pass the "self" object as the
first argument.  Lua has syntactic support for this with the : operator,
and it follows what languages like Java & C++ do.  This means that you
can refer to globals using "a" and fields of the object as "self.a".

Another option is to use the object (i.e. table) as the environment of
the function.  Then you refer to fields using "a", and globals using
"_G.a".  It would be easy enough to set up "global" as a synonym for "_G".

For an OO style while accessing fields is more common than globals, the
environment option seems cleaner than the standard : option.  Are there
any down sides that I'm missing?

Best,
Martin
Reply | Threaded
Open this post in threaded view
|

Re: Using environment for OO

troels knak-nielsen
On Fri, Apr 10, 2009 at 7:24 PM, Martin C. Martin
<[hidden email]> wrote:
> For an OO style while accessing fields is more common than globals, the
> environment option seems cleaner than the standard : option.  Are there any
> down sides that I'm missing?
>

"clean" is such a loaded term.

Obviously, your suggestion makes for an ambiguous syntax. Java doesn't
have this problem, because there is no global scope - only an object
scope. An additional issue in Lua, is that the same function may have
different roles in different situations (Some times as a member
function - some times not). I'm sure this will further complicate
matters/add to the ambiguity.

>From a technical viewpoint, I would venture to guess that it would
bare the cost of an extra lookup, which impacts performance.

--
troels
Reply | Threaded
Open this post in threaded view
|

Re: Using environment for OO

Asko Kauppi

I like the suggestion.  And it should be easy to disallow using  
globals (from within objects) completely, if needed.

I'd say - go for it!  :)

-asko


troels knak-nielsen kirjoitti 10.4.2009 kello 21:18:

> On Fri, Apr 10, 2009 at 7:24 PM, Martin C. Martin
> <[hidden email]> wrote:
>> For an OO style while accessing fields is more common than globals,  
>> the
>> environment option seems cleaner than the standard : option.  Are  
>> there any
>> down sides that I'm missing?
>>
>
> "clean" is such a loaded term.
>
> Obviously, your suggestion makes for an ambiguous syntax. Java doesn't
> have this problem, because there is no global scope - only an object
> scope. An additional issue in Lua, is that the same function may have
> different roles in different situations (Some times as a member
> function - some times not). I'm sure this will further complicate
> matters/add to the ambiguity.
>
>> From a technical viewpoint, I would venture to guess that it would
> bare the cost of an extra lookup, which impacts performance.
>
> --
> troels

Reply | Threaded
Open this post in threaded view
|

Re: Using environment for OO

David Given
In reply to this post by Martin C. Martin-2
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Martin C. Martin wrote:
[...]
> For an OO style while accessing fields is more common than globals, the
> environment option seems cleaner than the standard : option.  Are there
> any down sides that I'm missing?

Correct me if I'm wrong, but won't the same function (with one
environment) need to be shared among several different objects? Won't
you have to explicitly call setfenv() at the top of each method,
preventing them from being called reentrantly?

- --
┌─── dg@cowlark.com ───── http://www.cowlark.com ─────

│ "All power corrupts, but we need electricity." --- Diana Wynne Jones,
│ _Archer's Goon_
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFJ36h9f9E0noFvlzgRAhB3AKCll17SHhX4H985QHJIiWFGSKraKACgiIiU
UZuWXvTP18z81yD/xqMHK5o=
=OixL
-----END PGP SIGNATURE-----
Reply | Threaded
Open this post in threaded view
|

Re: Using environment for OO

Martin C. Martin-2
I want that to be transparent to the function definer, so my class
object looks like this:

MyClass = {
   __newindex = function (table, key, value)
     if type(value) == "function" then
       setfenv(value, table)
     end
     rawset(table, key, value)
   end
}

My "new" function takes a table to use for the object, so it goes
through it an calls setfenv() on each function, like this:

function MyClass.new(obj)
    obj = obj or {}
    for k, v in pairs(obj) do
      if type(v) == "function" then
        print ("Setting environment for ", k)
        setfenv(v, obj)
      end
    end
    setmetatable(obj, MyClass)
    MyClass.__index = MyClass
    return obj
end

For a class with no base class, I need to do this:

setmetatable(MyClass, {__index = _G})

Best,
Martin

David Given wrote:

> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> Martin C. Martin wrote:
> [...]
>> For an OO style while accessing fields is more common than globals, the
>> environment option seems cleaner than the standard : option.  Are there
>> any down sides that I'm missing?
>
> Correct me if I'm wrong, but won't the same function (with one
> environment) need to be shared among several different objects? Won't
> you have to explicitly call setfenv() at the top of each method,
> preventing them from being called reentrantly?
>
> - --
> ┌─── dg@cowlark.com ───── http://www.cowlark.com ─────
> │
> │ "All power corrupts, but we need electricity." --- Diana Wynne Jones,
> │ _Archer's Goon_
> -----BEGIN PGP SIGNATURE-----
> Version: GnuPG v1.4.9 (GNU/Linux)
> Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org
>
> iD8DBQFJ36h9f9E0noFvlzgRAhB3AKCll17SHhX4H985QHJIiWFGSKraKACgiIiU
> UZuWXvTP18z81yD/xqMHK5o=
> =OixL
> -----END PGP SIGNATURE-----
Reply | Threaded
Open this post in threaded view
|

Re: Using environment for OO

David Given
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Martin C. Martin wrote:
[...]
> My "new" function takes a table to use for the object, so it goes
> through it an calls setfenv() on each function[...]

But, assuming I've understood what you're talking about correctly, this
way you'll have to have a *different* function for every instance of the
object. So x.foo and y.foo cannot refer to the same function foo(), even
if x and y are instances of the same class.

[...]
> function MyClass.new(obj)

If I do:

o = { foo = function foo() end }
x = MyClass.new(o)
y = MyClass.new(o)

...then when x is created, o.foo's environment will be updated to point
to x; then when y is created, o.foo's environment will be updated to
point to y. Since x.foo == y.foo == o.foo, calling x.foo() will call the
method with y as the environment. I assume this isn't what you want?

(I could be misunderstanding what you mean here; I haven't used function
environments much beyond sandboxing.)

- --
┌─── dg@cowlark.com ───── http://www.cowlark.com ─────

│ "All power corrupts, but we need electricity." --- Diana Wynne Jones,
│ _Archer's Goon_
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFJ38Epf9E0noFvlzgRArsdAJ96CVY3yH/+Hyi0o6xmAPaU3GqTugCgkXWP
nEFwW4kxphE+xtsTTZ/LBfQ=
=w5Wr
-----END PGP SIGNATURE-----
Reply | Threaded
Open this post in threaded view
|

Re: Using environment for OO

Eike Decker
In reply to this post by Martin C. Martin-2
Maybe I am wrong but ... isn't your constructor going to just going to
set function environments for the functions that are already set for
the object (none if the object is new) and that the class functions
are not going to have the object's scope but the class' scope only?
Also, the __newindex function is not going to cope with functions that
are replacing old ones since __newindex is only called if a new key is
inserted - changing existing ones can't be monitored with that
metafunction.
If you would want to have the functions having the object's scope, you
will need to change the function's environment when the function is
being called. A more transparent system could be to provide a function
that allows a function to set a table as its new environment, i.e.

function foo(self)
  self.x = 21
  setscope(self)
  print(x) -- 21
end

This approach has the advantage that the function itself controls its
scoping - it doesn't depend on implementations of the underlaying OO
framework. It also provides a visual hint for readers that the scope
of this particular function is bound to an object. It involves less
"magically" operations that are triggered outside of the visual scope
of the programmer and it also allows programmers to decide if they
really want to use this way of programming that you suggest instead of
enforcing it. It is also compatible with the "default approach" for
implementing OO systems in Lua. I.e., you can also extend existing
code in the manner you want without changing the underlaying OO
mechanics of other modules you might use.
The scope function could also have instructions to disallow global
variable access and other stuff:

function foo (self) setscope(self) : globalscope "off" : readonly "on"
  _G.print(x)
  x = nil -- runtime error
end

Proove of concept code:

----
function setscope (obj)
        local func = debug.getinfo(2,"f").func
        local env = {}
        local mt = {}
        local readonly
        function mt:__index(key)
                if obj[key]~=nil then return obj[key] end
                return _G[key]
        end
        function mt:__newindex(key,value)
                assert(readonly~="on","attempt to write value in readonly mode")
                obj[key] = value
        end
        setmetatable(env,mt)
        setfenv(func,env)
        local mod = {}
        function mod:readonly(mode)
                readonly = mode
                return mod
        end
        return mod
end

account = {balance = 0}

function account:payin(money) setscope(self)
        assert(money>0,"expected a payin")
        balance = balance + money
end

function account:printbalance() setscope(self) : readonly "on"
        print(balance)
        balance = 0  -- error: attempt to write value in readonly mode
end

account:payin(100)
account:printbalance()
----

Eike

2009/4/10 Martin C. Martin <[hidden email]>:

> I want that to be transparent to the function definer, so my class object
> looks like this:
>
> MyClass = {
>  __newindex = function (table, key, value)
>    if type(value) == "function" then
>      setfenv(value, table)
>    end
>    rawset(table, key, value)
>  end
> }
>
> My "new" function takes a table to use for the object, so it goes through it
> an calls setfenv() on each function, like this:
>
> function MyClass.new(obj)
>   obj = obj or {}
>   for k, v in pairs(obj) do
>     if type(v) == "function" then
>       print ("Setting environment for ", k)
>       setfenv(v, obj)
>     end
>   end
>   setmetatable(obj, MyClass)
>   MyClass.__index = MyClass
>   return obj
> end
>
> For a class with no base class, I need to do this:
>
> setmetatable(MyClass, {__index = _G})
>
> Best,
> Martin
>
> David Given wrote:
>>
>> -----BEGIN PGP SIGNED MESSAGE-----
>> Hash: SHA1
>>
>> Martin C. Martin wrote:
>> [...]
>>>
>>> For an OO style while accessing fields is more common than globals, the
>>> environment option seems cleaner than the standard : option.  Are there
>>> any down sides that I'm missing?
>>
>> Correct me if I'm wrong, but won't the same function (with one
>> environment) need to be shared among several different objects? Won't
>> you have to explicitly call setfenv() at the top of each method,
>> preventing them from being called reentrantly?
>>
>> - --
>> ┌─── dg@cowlark.com ───── http://www.cowlark.com ─────
>> │
>> │ "All power corrupts, but we need electricity." --- Diana Wynne Jones,
>> │ _Archer's Goon_
>> -----BEGIN PGP SIGNATURE-----
>> Version: GnuPG v1.4.9 (GNU/Linux)
>> Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org
>>
>> iD8DBQFJ36h9f9E0noFvlzgRAhB3AKCll17SHhX4H985QHJIiWFGSKraKACgiIiU
>> UZuWXvTP18z81yD/xqMHK5o=
>> =OixL
>> -----END PGP SIGNATURE-----
>
Reply | Threaded
Open this post in threaded view
|

Re: Using environment for OO

Martin C. Martin-2
In reply to this post by David Given
You and Eike are right.  In my application, the classes I'm dealing with
only have a single object, so I mixed the fields in with the class.  Of
course, if I wanted to apply this to any class with more than a single
object, this won't work.  I'm surprised I didn't catch that myself --
guess my kids have been waking me up too early, too many days in a row
or something.  :)

Thanks,
Martin

David Given wrote:

> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> Martin C. Martin wrote:
> [...]
>> My "new" function takes a table to use for the object, so it goes
>> through it an calls setfenv() on each function[...]
>
> But, assuming I've understood what you're talking about correctly, this
> way you'll have to have a *different* function for every instance of the
> object. So x.foo and y.foo cannot refer to the same function foo(), even
> if x and y are instances of the same class.
>
> [...]
>> function MyClass.new(obj)
>
> If I do:
>
> o = { foo = function foo() end }
> x = MyClass.new(o)
> y = MyClass.new(o)
>
> ...then when x is created, o.foo's environment will be updated to point
> to x; then when y is created, o.foo's environment will be updated to
> point to y. Since x.foo == y.foo == o.foo, calling x.foo() will call the
> method with y as the environment. I assume this isn't what you want?
>
> (I could be misunderstanding what you mean here; I haven't used function
> environments much beyond sandboxing.)
>
> - --
> ┌─── dg@cowlark.com ───── http://www.cowlark.com ─────
> │
> │ "All power corrupts, but we need electricity." --- Diana Wynne Jones,
> │ _Archer's Goon_
> -----BEGIN PGP SIGNATURE-----
> Version: GnuPG v1.4.9 (GNU/Linux)
> Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org
>
> iD8DBQFJ38Epf9E0noFvlzgRArsdAJ96CVY3yH/+Hyi0o6xmAPaU3GqTugCgkXWP
> nEFwW4kxphE+xtsTTZ/LBfQ=
> =w5Wr
> -----END PGP SIGNATURE-----