cross version compatible module definition

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
User avatar
Inny
Party member
Posts: 652
Joined: Fri Jan 30, 2009 3:41 am
Location: New York

cross version compatible module definition

Post by Inny »

The search form didn't answer my question so I'll start a thread about it, and I'm guessing Kikito is the one I'd like to answer it, but everyone else is welcome to share opinions of course. Also, as far as LoVE is concerned, we're locked into the 5.1 world via luajit, so this isn't a thing to worry about for most people. I'm writing a module of all of the things I consider to be useful to me from my time in the javascript world. The inspiration is from moses.lua, luafun, underscore.lua, etc. Please go review and trust them before using anything you get out of me, they actually unit test. :crazy:

For me, I'd like to know what's a good pattern to use for modules that would not only work in luajit, but 5.2/5.3 (in case I bring this code over to lua.io, or any other node clone). The feature in particular I'm after is simulating _ENV's safety. I think this it the way to go:

Code: Select all

local module = function(_ENV)
  __ = {}

  __.reduce = function(fn, array, initial)
    local result = initial or 0
    for i = 1, #array do
      result = fn(result, array[i], i)
    end
    return result
  end

  return __
end

local environment = setmetatable({}, {__index = _G})
if setfenv then setfenv(module, environment) end
return module(environment)
The question is: is this a terrible idea? What am I forgetting? Is checking for setfenv enough to discover 5.1? What should I change?
nobody
Prole
Posts: 3
Joined: Wed Aug 27, 2014 7:57 pm

Re: cross version compatible module definition

Post by nobody »

Inny wrote:Is checking for setfenv enough to discover 5.1?
No. Quite a few people define setfenv / getfenv as compatibility functions (or use modules that provide these.) (Not terribly many, but if you do this, it'll probably break for someone eventually.) (OTOH, if you do stuff that doesn't break if someone defined setfenv, then you don't need to care. And I think you can assume that if the current interpreter doesn't have _ENV for environments, then it will have setfenv.)

If you need to check the version, check _VERSION ! E.g. tonumber( _VERSION:sub( -3, -1 ) ) < 5.2

Next, ...
Inny wrote:What am I forgetting? What should I change?

Code: Select all

local module = function(_ENV)
  __ = {}

  __.reduce = function(fn, array, initial)
    local result = initial or 0
    for i = 1, #array do
      result = fn(result, array[i], i)
    end
    return result
  end

  return __
end

local environment = setmetatable({}, {__index = _G})
if setfenv then setfenv(module, environment) end
return module(environment)
Under 5.1 semantics, your function should work but it's rather strange in style. Remember that your file is already a function, so everything that happens in the module body function is essentially defined two functions deep. (That's not a problem, just strange.)

(I haven't touched 5.0 in a looong time, so I hope it's dead enough not to matter.)

Under 5.2 or 5.3 semantics, your function should also work. (And again, strange style.)

What about this?

Code: Select all

local _ENV = setmetatable( { }, { __index = _G } )
if setfenv then  setfenv( 1, _ENV )  end
-- or: if tonumber( _VERSION:sub( -3, -1 ) ) < 5.2 then …
--   but setting the environment twice doesn't break anything
--   (For reference: in 5.2+, setfenv is commonly implemented via debug.setupvalue
--   and finding & replacing the right [i]_ENV[/i].)

reduce = function( fn, array, initial )  …  end

-- or, if you prefer typing more to prefix all names:
local __ = _ENV
__.reduce = function( fn, array, initial )  …  end

-- etc.

return _ENV
Inny wrote:For me, I'd like to know what's a good pattern to use for modules that would not only work in luajit, but 5.2/5.3 (in case I bring this code over to lua.io, or any other node clone). The feature in particular I'm after is simulating _ENV's safety.
For small stuff, I usually use the thing I described above. But I quite liked the idea of the original module function of Lua. (Except for the part where it always puts something into _G without caring about what's been there before.)

So for larger stuff, I have my own Module function that's similar to module. (You call it and then you're in the module's scope.) The main differences are that it doesn't clutter _G and that modules have two tables – one module table and one environment table. (I dislike that the __index trick makes everything visible through the module.) So there's _M for the actual exposed module and _MENV for the environment, both linked to each other like _MENV._M, _M._MENV = _M, _MENV (and the __index=G on _MENV doesn't influence _M). This means I don't need to prefix all internal stuff with local, so accidentally forgetting that doesn't matter. (Actually, I intentionally don't prefix stuff with local, because that makes it easier to reach internals for debugging – instead of looping & poking around with debug.getlocal to find the variables, I can just say mymodule._MENV.foo to get the mymodule's internal foo.)

If you want to, I can try to extract that from my init files (but it might take a while… and it's probably overkill.)
User avatar
Inny
Party member
Posts: 652
Joined: Fri Jan 30, 2009 3:41 am
Location: New York

Re: cross version compatible module definition

Post by Inny »

Code: Select all

local _ENV = setmetatable( { }, { __index = _G } )
if setfenv then  setfenv( 1, _ENV )  end
Aha! I forgot that the 1st parameter on setfenv referred to the current function, though how would a 5.2 polyfill cope with it? It may be better to check _VERSION explicitly, even though I generally hate depending on something like that.
nobody
Prole
Posts: 3
Joined: Wed Aug 27, 2014 7:57 pm

Re: cross version compatible module definition

Post by nobody »

Inny wrote:Aha! I forgot that the 1st parameter on setfenv referred to the current function, though how would a 5.2 polyfill cope with it?…
My setfenv-reimplementation checks what arg#1 is and if it's a number (=stack level), then it uses debug.getinfo( lvl, "f" ).func as the function. (Plus rather annoying offset-computations to make it look as if the internally called functions aren't on the stack. Plus checking whether that functions actually has an environment, etc. etc. And I think it still behaves differently in some cases.)

As for what other reimplementations do, absolutely no idea. ^^ (I have seen some really simple special-purpose ones, some of which will only work for stack levels and break for functions, and others that only work for functions and break for stack levels, and of course people decide to put these in _G… I don't know if it's worth caring about these, the last time I saw one of these was… many moons ago. And if someone actually still uses broken stuff it's not too hard to replace the broken function with a better one or just removing it temporarily to load the module.)

So yeah, if you want to handle these cases then check _VERSION, otherwise… just let it crash. :ultraglee:
User avatar
Robin
The Omniscient
Posts: 6506
Joined: Fri Feb 20, 2009 4:29 pm
Location: The Netherlands
Contact:

Re: cross version compatible module definition

Post by Robin »

nobody wrote:If you need to check the version, check _VERSION ! E.g. tonumber( _VERSION:sub( -3, -1 ) ) < 5.2
Versions are not numbers. This will break with version 5.10 of Lua.
Help us help you: attach a .love.
User avatar
undef
Party member
Posts: 438
Joined: Mon Jun 10, 2013 3:09 pm
Location: Berlin
Contact:

Re: cross version compatible module definition

Post by undef »

Robin wrote:
nobody wrote:If you need to check the version, check _VERSION ! E.g. tonumber( _VERSION:sub( -3, -1 ) ) < 5.2
Versions are not numbers. This will break with version 5.10 of Lua.
It doesn't say _VERSION<5.2 but tonumber( _VERSION:sub( -3, -1 ) )<5.2, so it works
twitter | steam | indieDB

Check out quadrant on Steam!
User avatar
bartbes
Sex machine
Posts: 4946
Joined: Fri Aug 29, 2008 10:35 am
Location: The Netherlands
Contact:

Re: cross version compatible module definition

Post by bartbes »

And for lua 5.10 that returns 0.1.
User avatar
undef
Party member
Posts: 438
Joined: Mon Jun 10, 2013 3:09 pm
Location: Berlin
Contact:

Re: cross version compatible module definition

Post by undef »

bartbes wrote:And for lua 5.10 that returns 0.1.
Ah, I see...
But 0.1<5.2 is true, so why would it break?
twitter | steam | indieDB

Check out quadrant on Steam!
User avatar
bartbes
Sex machine
Posts: 4946
Joined: Fri Aug 29, 2008 10:35 am
Location: The Netherlands
Contact:

Re: cross version compatible module definition

Post by bartbes »

Because 5.10 is a later version than 5.2?
User avatar
Inny
Party member
Posts: 652
Joined: Fri Jan 30, 2009 3:41 am
Location: New York

Re: cross version compatible module definition

Post by Inny »

With regards to _VERSION, you can't future proof a string, because it's not yet been defined for future versions of Lua. Plus it's bad form to check a version string or number anyway, because it doesn't allow for polyfills (like table.pack is a 5.2 feature, but has a very easy polyfill in 5.1).

I'm fine with crashing on a broken version of setfenv.
Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot], Google [Bot] and 4 guests