Modul:require when needed

local getmetatable = getmetatable
local ipairs = ipairs
local loaded = package.loaded
local pairs = pairs
local require = require
local select = select
local setmetatable = setmetatable
local tostring = tostring
local unpack = unpack

local function get_nested(obj, ...)
	local n = select("#", ...)
	if n == 0 then
		return obj
	end
	obj = obj[...]
	for i = 2, n do
		obj = obj[select(i, ...)]
	end
	return obj
end

local function get_obj(mt)
	local obj = require(mt[1])
	if #mt > 1 then
		obj = get_nested(obj, unpack(mt, 2))
	end
	mt[0] = obj
	return obj
end

local function __call(self, ...)
	local mt = getmetatable(self)
	local obj = mt[0]
	if obj == nil then
		obj = get_obj(mt)
	end
	return obj(...)
end

local function __index(self, k)
	local mt = getmetatable(self)
	local obj = mt[0]
	if obj == nil then
		obj = get_obj(mt)
	end
	return obj[k]
end

local function __ipairs(self)
	local mt = getmetatable(self)
	local obj = mt[0]
	if obj == nil then
		obj = get_obj(mt)
	end
	return ipairs(obj)
end

local function __newindex(self, k, v)
	local mt = getmetatable(self)
	local obj = mt[0]
	if obj == nil then
		obj = get_obj(mt)
	end
	obj[k] = v
end

local function __pairs(self)
	local mt = getmetatable(self)
	local obj = mt[0]
	if obj == nil then
		obj = get_obj(mt)
	end
	return pairs(obj)
end

local function __tostring(self)
	local mt = getmetatable(self)
	local obj = mt[0]
	if obj == nil then
		obj = get_obj(mt)
	end
	return tostring(obj)
end

return function(modname, ...)
	local mod = loaded[modname]
	if mod ~= nil then
		return get_nested(mod, ...)
	end
	return setmetatable({}, {
		modname,
		__call = __call,
		__index = __index,
		__ipairs = __ipairs,
		__newindex = __newindex,
		__pairs = __pairs,
		__tostring = __tostring,
		-- TODO: other metamethods, if needed.
		...
	})
end