This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Eric's profile picture

Better `os.loadAPI`

Started by Eric, 16 December 2012 - 02:56 AM
Eric #1
Posted 16 December 2012 - 03:56 AM
Here's one of the things that annoys me with the current os.loadAPI:

My api file:

x = 2
setX = function(value) x = value end
getX = function() return x end

Main script:

os.loadAPI('myapi')
print(myapi.x) -- 2
myapi.setX(3)
print(myapi.x) -- 2
print(myapi.getX()) -- 3

This happens because globals are copied into the API object, while the functions still refer to the original variable. The solution here is to use metatables, to proxy access to globals into the API table. Here's some code that fixes the important bit:

-- set up a function environment with access to _G
local tAPI = {}
local tEnv = setmetatable({}, {
	__index = function(_, k, v)
		if tAPI[k] ~= nil then
			return tAPI[k]
		elseif k == "module" then
			return tAPI
		else
			return _G[k]
		end
	end,
	__newindex = tAPI
})
This also allows modules to reference themselves with the global `module`, allowing modules to be renamed more easily.

The full code can be found on github. I've made a few more changes:
  • If a module errors, it doesn't prevent it being loaded in future
  • Option to force module reloading
  • Distinction between loading and loaded
but it works in fundamentally the same way as the original.
Sebra #2
Posted 16 December 2012 - 06:33 AM
Look and look.
Your "force" method is fun, but predefine word "module" is not.
No offence here ;)/> (just in case)
Eric #3
Posted 16 December 2012 - 07:48 AM
but predefine word "module" is not.

How so? In hindsight, I think I have the lookup precedence wrong: `tAPI.module`, if you're mad enough to override it, should take precedence over `module`. I'll update the snippet above.

Also, there's more to this than being able to set a metatable on the module (which is less important). The crucial part is that it doesn't break closures, since variables aren't copied into the API table.

Almost all the implementations in the thread you linked to either:
  • Copy members rather than proxying (breaks closures)
  • Makes the module and environment one and the same (makes atrocities like _G.myapi.someotherapi._G.myapi.math valid)
Sebra #4
Posted 16 December 2012 - 08:41 AM
You "eat" a word. It is not needed and not obvious.

In my version of loadAPI code gives out prepared result itself. Old way remains for compatibility. Isn`t it better? I can agree with "force" and "unload" options.
Eric #5
Posted 16 December 2012 - 09:05 AM
You "eat" a word. It is not needed and not obvious.

With the edit I made above, it does not "eat" a word. If you want your API to contain a `module` member, you can simply override it. If you want to access a global `module` variables, you can use `_G.module`.

It is needed in some cases, for it allows:

setmetatable(module, ...)
module["string key"]

In my version of loadAPI code gives out prepared result itself
Not sure what you mean here. Are you referring to this post?

Edit: Oh, I see - you're suggesting that a module file should just return the module object. Yes, that a very reasonable approach.
immibis #6
Posted 16 December 2012 - 01:03 PM
Also, there's more to this than being able to set a metatable on the module (which is less important). The crucial part is that it doesn't break closures, since variables aren't copied into the API table.
Only locals make closures. When you access a global, it gets looked up in the current environment every time you use it.
Eric #7
Posted 16 December 2012 - 01:18 PM
When you access a global, it gets looked up in the current environment every time you use it.
You could say that the functions create a closure on the environment itself in which they're defined.
immibis #8
Posted 16 December 2012 - 01:26 PM
When you access a global, it gets looked up in the current environment every time you use it.
You could say that the functions create a closure on the environment itself in which they're defined.
You can change the environment though.
Eric #9
Posted 16 December 2012 - 01:34 PM
You can change the environment though.
That's a very good point (and possibly another way of solving the problem).