
Глобальный контекст
Все глобальные переменные являются полями обычной таблицы, называемой глобальным контекстом. Эта таблица доступна через глобальную переменную _G. Поскольку все глобальные переменные являются полями контекста, то _G._G == _G.
Глобальный контекст делает возможным доступ к глобальным переменным по динамически генерируемому имени:
val = _G[varname] _G[varname] = val |
Поскольку глобальный контекст является обычной таблицей, ему может соответствовать мета-таблица. В следующем примере переменные окружения вводятся в глобальную область видимости как глобальные переменные, доступные только для чтения:
local f = function (t,i) return os.getenv(i) end
setmetatable(_G, {__index=f}) |
Этот же прием позволяет запретить доступ к неинициализированным глобальным переменным.
Пакеты
Пакеты — основной способ определять набор взаимосвязанных функций, не загрязняя при этом глобальную область видимости. Обычно пакет представляет собой отдельный файл, который в глобальной области видимости определяет единственную таблицу, содержащую все функции этого пакета:
my_package = {}
function my_package.foo() ... end |
Также можно делать все функции локальными и отдельно формировать таблицу экспортируемых функций:
local function foo() ... end local function bar() ... end
my_package = { foo = foo, bar = bar, } |
Пакет загружается с помощью функции require(), причем во время загрузки имя, переданное этой функции (оно может не содержать расширения, которое добавляется автоматически) доступно через переменную _REQUIREDNAME:
if _REQUIREDNAME == nil then run_some_internal_tests() end |
Классы и объекты
Конструкция tbl:func() (при объявлении функции и при ее вызове) предоставляет основные возможности, позволяющие работать с таблицей как с объектом. Основная проблема состоит в порождении многих объектов, имеющих сходное поведение, т.е. порожденных от одного класса:
function class() cl = {} cl.__index = cl -- cl будет использоваться как мета-таблица return cl end
function object( cl, obj ) obj = obj or {} -- возможно уже есть заполненные поля setmetatable(obj, cl) return obj end |
Здесь функция class создает пустую таблицу, подготовленную к тому, чтобы стать мета-таблицей объекта. Методы класса делаются полями этой таблицы т.е. класс является таблицей, одновременно содержащей методы объекта и его мета-методы. Функция object() создает объект заданного класса — таблицу, у которой в качестве мета-таблицы установлен заданный класс. Во втором аргументе может быть передана таблица, содержащая проинициализированные поля объекта.
Some_Class = class()
function Some_Class:foo() ... end
function Some_Class:new() return object(self, { xxx = 12 }) end
x = Some_Class:new()
x:foo() |
Наследование
В описанной реализации мета-таблица класса остается не использованной, что делает простой задачу реализации наследования. Класс-наследник создается как объект класса, после чего в нем устанавливающая поле __index таким образом, чтобы его можно было использовать как мета-таблицу:
function subclass( pcl ) cl = pcl:new() -- создаем экземпляр cl.__index = cl -- и делаем его классом return cl end |
Теперь в полученный класс-наследник можно добавить новые поля и методы:
Der_Class = subclass(Some_Class)
function Der_Class:new() local obj = object(self, Some_Class:new()) obj.yyy = 13 -- добавляем новые поля return obj end
function Der_Class:bar() ... end -- и новые методы
y = Der_Class:new()
y:foo() y:bar() |
Единственный нетривиальный момент здесь состоит в использовании функции new() из класса-предка с последующей заменой мета-таблицы посредством вызова функции object().
При обращении к методам объекта класса-наследника в первую очередь происходит их поиск в мета-таблице т.е. в самом классе-наследнике. Если метод был унаследован, то этот поиск окажется неудачным и произойдет обращение к мета-таблице класса-наследника т.е. к классу-предку.
Основной недостаток приведенного общего решения состоит в невозможности передачи параметров в функцию new() класса-предка.