/
d
e
v
/
t
o
k
i
o
r
y
Блог <tokiory>
В
в
е
д
е
н
и
е
в
L
u
a
Рассмотрим Lua — один из самых маленьких и простых, но от этого не менее интересных языков программирования.

Lua - это легкий, высокоуровневый, мультипарадигменный язык программирования с динамической типизацией и автоматическим управлением памятью. Он был разработан в начале 1990-х годов в Католическом университете Рио-де-Жанейро (Бразилия) и изначально предназначался для встраивания в другие программы как скриптовый язык.

С самого начала Lua показался мне совсем невыразительным языком, можно сказать, что в первое время я считал его “игрушечным”. В свете того, что мне нужно было переписывать конфигурацию для Neovim — мне пришлось посмотреть как работать с этим языком и хотя бы немного подучить его. После того как я написал конфигурацию для редактора, я решил также написать шаблон конфигурации. Сам того не замечая, мне начал все больше нравиться этот язык и моё мнение о нем изменилось.

Где используется Lua?

  • Lua популярен как встроенный язык сценариев для других языков и платформ, таких как C/C++, Java, Python. Его встраиваемость позволяет расширять функциональность приложений без перекомпиляции основного кода.
  • Lua широко используется для написания сценариев (скриптов) в компьютерных играх. Он управляет игровой логикой, искусственным интеллектом, диалогами, анимацией и другими аспектами поведения объектов.
  • Lua применяется в веб-фреймворках (например, Corona SDK, Marmalade SDK) и популярных веб-приложениях (WordPress, OpenCart, Joomla) для добавления динамических функций;
  • Lua используется для создания интерфейсов и расширения функциональности программ, например, в графическом редакторе Adobe Lightroom.
  • Благодаря компактности Lua применяется в портативных устройствах, например, в графических калькуляторах Texas Instruments TI-Nspire CX.

В общем-то, Lua применяется в разных областях и справляется с совершенно разными задачами благодаря его простоте, портативности, легкости встраиваемости и скорости выполнения.

Среда выполнения

Lua — интерпретируемый язык программирования, поэтому нам нужен интерпретатор Lua, чтобы запускать код. Мы можем установить его с помощью пакетного менеджера:

# MacOS
brew install lua

# Ubuntu
sudo apt install lua5.3

# Fedora/RHEL
sudo dnf install lua

# Arch Linux
sudo pacman -S lua

# Windows
choco install lua53

# Unix-подобные системы
asdf plugin add lua
asdf install lua latest

Чтобы запустить интерпретатор Lua в терминале, достаточно ввести команду lua.

Основы синтаксиса

Если бы можно было описать синтаксис Lua в двух словах, то я описал бы его как “внебрачный сын Bash и Python”. Разработчики Lua пытались сделать язык простым для того чтобы люди, которые не знакомы или мало знакомы с программированием смогли на нем писать, отсюда и болячки, такие как “индексы начинаются с единицы” и локальные переменные объявляются с помощью ключевого слова local, вместо какого-нибудь краткого let или var.

Мы начнем изучение с основ и доберемся до пакетного менеджера, так что впереди много всего интересного.

Комментарии

Комментарии в Lua начинаются с символа -- и продолжаются до конца строки. Многострочные комментарии начинаются с --[[ и заканчиваются на ]]:

-- Однострочный комментарий

--[[
    Многострочный комментарий
    Он может занимать несколько строк
]]

Переменные

Переменные в Lua не имеют явной типизации и бывают двух типов: локальные и глобальные. Локальные переменные объявляются с помощью ключевого слова local, тогда как глобальные – объявляются без него:

local foo = 'Обычная строчная переменная'
PI = 3.1415

В примере вверху foo является локальное переменной и ограничивается областью видимости скоупа или файла. PI — глобальная переменная, она будет доступна на протяжении всего выполнения программы из любого файла.

Именование переменных

По умолчанию в Lua нет стандарта по именованию переменных, однако, стандартная библиотека написана с использованием snake_case и UPPER_SNAKE_CASE, поэтому нам тоже стоит придерживаться этого соглашения по именованию переменных и сущностей.

Константы

До версии Lua 5.6 в языке не существовало такого понятия, как константа, однако, в версии 5.6 появилась аннотация <const> для создания констант, вот как выглядит данная конструкция:

local PI <const> = 3.1415

Типы данных

Как уже было сказано, в Lua нет строгой типизации, и переменные могут принимать значения разных типов. Однако, в Lua есть несколько базовых типов данных, которые мы должны держать в голове при работе с переменными и их значениями:

Тип данных Описание
nil Специальный тип, который обозначает отсутствие значения. Он используется для инициализации переменных и обозначения пустых значений.
boolean Логический тип, который может принимать значения true или false.
number Числовой тип, который представляет собой числа с плавающей запятой двойной точности (64 бита). Lua не различает целые и вещественные числа, все они представляются как number.
string Строковый тип, который представляет собой последовательность символов. Строки в Lua неизменяемы.
function Тип, представляющий функции. Lua поддерживает функции как объекты первого класса, что позволяет передавать их как аргументы и возвращать из других функций.
table Ассоциативный массив, который может содержать значения любого типа. Таблицы в Lua являются основным способом организации данных и представляют собой универсальную структуру данных.
thread Тип, представляющий легковесные потоки выполнения. Lua поддерживает многопоточность с помощью легковесных потоков, которые позволяют выполнять несколько задач одновременно.
userdata Тип, представляющий произвольные данные, которые могут быть созданы и использованы в Lua. Он позволяет взаимодействовать с данными, созданными на стороне C или других языков.

Числа, строки и булевы значения

Числа в Lua представляют собой числа с плавающей запятой двойной точности (64 бита). Любое число в Lua будет размерностью в 64 бита, вне зависимости от того содержит оно плавающую точку или нет, это сделано для упрощения обращения с числами. Давайте рассмотрим пример:

local foo = 10     -- Целочисленное число // number
local bar = 10.2   -- Дробное число // тоже number
print(foo + bar)   -- 20.2

Строки в Lua представляют собой последовательности символов и могут быть заключены в одинарные или двойные кавычки. Строки в Lua неизменяемы, что означает, что мы не можем изменять их содержимое после создания. Однако, мы можем создавать новые строки на основе существующих:

local str1 = 'Hello'
local str2 = "World"
local str3 = str1 .. ' ' .. str2 -- Конкатенация строк
print(str3) -- Hello World
Конкатенация строк

Конкатенация строк в Lua осуществляется с помощью оператора .., который объединяет две строки в одну. Важно помнить, что строки в Lua неизменяемы, поэтому конкатенация создает новую строку, а не изменяет существующую.

Если вы примените оператор .. к числам, Lua автоматически преобразует их в строки:

print(10 .. 25) -- 1025

Булевы значения в Lua представлены значениями true и false. Да и в целом, в Lua любое значение, кроме nil и false, считается истинным. Это означает, что даже числа и строки могут использоваться в логических выражениях.

Также, стоит отметить что в Lua нет оператора ! (логическое отрицание), вместо него используется оператор not, который возвращает true, если значение ложно, и false, если значение истинно.

local a = 10
local b = 'Hello'
local c = true
local d = false
local e = 0
local f = nil


--[[
  Мы будем использовать двойное отрицание, для того чтобы выяснить
  к какому логическому значению приводится переменная
]]
print("a истинно? " .. not not a) -- true
print("b истинно? " .. not not b) -- true
print("c истинно? " .. not not c) -- true
print("d истинно? " .. not not d) -- false
print("e истинно? " .. not not e) -- false
print("f истинно? " .. not not f) -- false

Таблицы

Одним из интересных типов данных является таблица. Данный тип данных представляет собой ассоциативный массив, который может содержать значения любого типа. Таблицы в Lua являются основным способом организации данных и представляют собой универсальную структуру данных, которая совмещает в себе функции и массивов, и объектов:

local foo = { "Один", "Два", "Три" }           -- Массив
local bar = { username = "tokiory", age = 22 } -- Ассоциативный массив (объект)

Более того, мы можем инициализировать массив и объект в одной таблице:

local info = { "tokiory", 22, site = "https://tokiory.vercel.app" }

Для того чтобы обратиться к элементу таблицы, мы можем использовать квадратные скобки или точечную нотацию:

local info = { "tokiory", 22, site = "https://tokiory.vercel.app" }

print(info[1]) -- tokiory
print(info[2]) -- 22
print(info.site) -- https://tokiory.vercel.app
Индексы начинаются с единицы

Да, индексы в Lua начинаются с единицы. Придется смириться с этим, так как язык был ориентирован на людей, которые не знакомы с программированием. В Lua нет возможности изменить это поведение, так как оно является частью языка.

Мы также можем явно указать индекс, когда инициализируем таблицу:

local info = {
    [1] = "tokiory",
    [2] = 22,
    site = "https://tokiory.vercel.app"
}

print(info[1]) -- tokiory
print(info[2]) -- 22
print(info.site) -- https://tokiory.vercel.app

Если же мы попробуем обратиться к элементу, которого нет в таблице, то Lua вернет nil:

local foo = {}
print(foo[1]) -- nil

Для того чтобы получить длину таблицы, мы можем использовать оператор #, который возвращает количество элементов в таблице:

local foo = { "Один", "Два", "Три" }
print(#foo) -- 3

Управление потоком выполнения

В Lua реализованы все базовые конструкции управления потоком выполнения, среди которых:

  • Условия: if, elseif, else
  • Циклы: for, while, repeat
  • Переходы: break, goto
  • Функции: function, return

Условия

Давайте начнем с условий, вот короткий пример применения конструкции if/elseif/else:

local a = 100

if a > 0 then
    print('a больше нуля')
elseif a < 0 then
    print('a меньше нуля')
else
    print('a равно нулю')
end

Примечательно, что Lua в данном случае совмещает синтаксис из Bash с его then и синтаксис из Python с его отсутствием фигурных и круглых скобок для ограничения блока кода. В Lua также есть конструкция elseif, которая позволяет проверять несколько условий подряд.

Циклы

Циклы в Lua реализованы с помощью трёх конструкций:

  • while — выполняет блок кода, пока условие истинно.
  • repeat — выполняет блок кода хотя бы один раз, а затем проверяет условие.
  • for — выполняет блок кода заданное количество раз или итерируется по таблице.

Цикл while

Для начала давайте рассмотрим самый простой цикл — while. Данная форма записи цикла позволяет выполнять блок кода до тех пор, пока условие истинно:

-- Пример бесконечного цикла
while true do
    print('Это бесконечный цикл')
end

-- Пример цикла с условием
local a = 10
while a > 0 do
    print(a)
    a = a - 1 -- уменьшаем значение a на 1
end

Цикл repeat

Теперь давайте рассмотрим repeat, он выполняет блок кода хотя бы один раз, а затем проверяет условие, в других языках (например, как Javascript) данный цикл известен под названием do while. Если условие истинно, цикл продолжается:

-- Пример бесконечного цикла
repeat
    print('Это бесконечный цикл')
until false

-- Пример цикла с условием
local a = 10
repeat
    print(a)
    a = a - 1 -- уменьшаем значение a на 1
until a == 0 -- цикл продолжается, пока a не станет равным 0

Цикл for

Ну, и наконец, давайте рассмотрим самый продвинутый цикл — for. Цикл for в Lua позволяет задавать начальное значение, конечное значение и шаг. Если шаг не указан, он по умолчанию равен 1:

-- Перечисление чисел от 1 до 10 (с шагом по умолчанию — 1)
for i = 1, 10 do
    print(i)
end

-- Перечисление чисел от 10 до 1 (с шагом в -2)
for i = 10, 1, -2 do
    print(i)
end

Итерация по таблицам

В Lua также есть возможность итерироваться по таблицам с помощью функции pairs или ipairs. Функция pairs позволяет итерироваться по всем элементам таблицы, а ipairs — только по числовым индексам:

local info = {
    "tokiory",
    22,
    site = "https://tokiory.vercel.app"
}

-- Итерация по всем элементам таблицы
for key, value in pairs(info) do
    print(key, value)
end

--[[
  1 tokiory
  2 22
  "site" https://tokiory.vercel.app
]]

В данном примере кода мы итерируемся по всем элементам таблицы info с помощью функции pairs. Мы получаем ключ и значение каждого элемента таблицы, которые затем выводим на экран.

local info = {
    "tokiory",
    22,
    site = "https://tokiory.vercel.app"
}

-- Итерация только по числовым индексам
for index, value in ipairs(info) do
    print(index, value)
end

--[[
  1 tokiory
  2 22
]]

В данном примере кода мы итерируемся только по числовым индексам таблицы info с помощью функции ipairs. Мы получаем индекс и значение каждого элемента таблицы, которые затем выводим на экран.

В данном случае мы получаем только числовые индексы 1 и 2, так как функция ipairs пропускает все ключи, которые не являются числами.

1 / 0
Свайпайте для просмотра слайдов

Стоит отметить, что ipairs будет проходиться по массиву до тех пор, пока не найдет первый nil, в то время как pairs будет проходиться по всем элементам таблицы:

local foo = {
    "tokiory",
    22,
    site = "https://tokiory.vercel.app",
    "bar",
    nil
    "foo"
}

for index, value in ipairs(foo) do
    print(index, value)
end

--[[
  1 tokiory
  2 22
  3 "bar"
]]

-- (4 nil) и (5 "foo") не выведутся, так как ipairs обнаружил nil в качестве значения

Функции

Функции в Lua являются объектами первого класса, что означает, что мы можем передавать их как аргументы, возвращать из других функций и хранить в переменных. Функции могут быть определены с помощью ключевого слова function, а затем вызваны по имени:

-- Определение функции
local function greet(name)
    print("Hello, " .. name .. "!")
end

-- Вызов функции
greet("tokiory") -- Hello, tokiory!

Глобальные и локальные функции

Функции в Lua могут быть глобальными или локальными. Глобальные функции объявляются без ключевого слова local, тогда как локальные функции объявляются с ним:

function global_func()
end

local function local_func()
end

Замыкания

Замыкание — это механизм позволяющий функции использовать контекст, в котором она была создана.

Это означает, что функция может использовать переменные из внешней области видимости даже после того, как эта область видимости была завершена:

local createGreeter = function(greeting)

    -- Данная функция может использовать параметр greeting из внешней функции
    return function(name)
        print(greeting .. ", " .. name .. "!")
    end
end

local greetHello = createGreeter("Hello")
local greetHi = greetHello("Daniil") -- Hello, Daniil!

Коллбэки

Коллбэки — это функции, которые передаются в качестве аргумента другим функциям и вызываются внутри них. Это позволяет создавать более гибкие и настраиваемые функции:

local printer = function(message)
    print(message)
end

local functionWithCallback = function(callback)
    callback("Hello, World!")
end

functionWithCallback(printer)
-- Hello, World!

Варидические функции

Варидические функции — это функции, которые могут принимать переменное количество аргументов. В Lua это достигается с помощью ..., который представляет собой список аргументов:

local function add(...)
    local sum = 0
    for _, value in ipairs({...}) do
        sum = sum + value
    end
    return sum
end

print(add(1,2,3,4,5)) -- 15

Также, функции в Lua могут возвращать сколько угодно значений, используя оператор return. Если функция не возвращает значения, то она вернет nil:

local function nillish()
end

nillish() -- nil

local function foo()
    return 1, 2, 3 -- Возвращаем кортеж значений
end

local a, b, c = foo() -- a = 1, b = 2, c = 3

Вариадические функции и кортежи

Синтаксис {...} позволяет разворачивать список аргументов в таблицу, что позволяет нам использовать функции ipairs и pairs для итерации по ним. Это полезно, когда мы хотим передать переменное количество аргументов в другую функцию или обработать их как массив.

Корнеркейс с nil

Важно понимать, что если в аргументах есть nil, то мы не сможем использовать ipairs, так как он остановится на первом nil.

Для того чтобы справляться с кейсами, где в аргументах есть nil была придумана функция select.

Данная функция позволяет нам выбирать элементы из списка аргументов, начиная с определенного индекса. Она принимает два аргумента: индекс и список аргументов.

Индекс может быть положительным или отрицательным. Если индекс положительный, то функция вернет все элементы, начиная с этого индекса. Если индекс отрицательный, то функция вернет все элементы, кроме тех, которые находятся до этого индекса:

local function getFromSecond(...) {
  return select(2, ...)
}

local function getLast(...)
  return select(-1, ...)
}

local a, b, c = getFromSecond(1, 2, 3, 4, 5) -- a = 2, b = 3, c = 4
local d = getLast(1, 2, 3, 4, 5) -- d = 5

В случае использования getFromSecond мы на самом деле получили четыре значения, но присвоили их только в три переменные, поэтому c будет равен 4, а 5 будет проигнорирован.

В случае с getLast мы получили только одно значение, которое присвоили переменной d.

Также, с помощью select мы можем получить количество аргументов, переданных в функцию, используя #:

local function getCount(...)
    return select('#', ...)
end

local count = getCount(1, 2, 3, 4, 5) -- count = 5

Для того чтобы обойти ограничение с nil и ipairs нам достаточно перебрать аргументы с помощью select:

local function printArgs(...)
    for i = 1, select('#', ...) do -- Перебираем все индексы кортежа
        -- Возвращаем все значения кортежа начиная с i,
        -- но игнорируем все остальные значения, кроме первого, который вернулся
        local element = select(i, ...)
        print(element)
    end
end

Методы

В Lua функции могут быть определены как методы, которые могут быть вызваны на объектах.

Методы — это функции, которые определены внутри таблицы и могут использоваться для работы с данными этой таблицы.

Мы можем определить методы двумя способами, первый из них является более распространенным и используется чаще:

local person = {
    name = "tokiory",
    age = 22,
    -- Инициализируем метод
    greet = function(self)
        print("Hello, my name is " .. self.name .. " and I'm " .. self.age .. " years old.")
    end
}

Мы можем вызвать метод, используя точечную нотацию:

person.greet() -- Hello, my name is tokiory and I'm 22 years old.

Вторым методом является использование оператора :, который позволяет нам передать объект в качестве первого аргумента функции автоматически. Это позволяет нам не передавать объект вручную, а использовать его как параметр с названием self внутри метода:

local person = {
    name = "tokiory",
    age = 22,
}

-- Инициализируем метод
function person:greet()
    print("Hello, my name is " .. self.name .. " and I'm " .. self.age .. " years old.")
end

-- Вызов метода
person:greet() -- Hello, my name is tokiory and I'm 22 years old.

Метатаблицы и ООП

Метатаблицы — это специальные таблицы, которые позволяют изменять поведение других таблиц. Они позволяют переопределять стандартные операции, такие как сложение, вычитание, сравнение и другие.

Метатаблицы используются для создания пользовательских типов данных и реализации объектно-ориентированного программирования. Они отличаются от обычных таблиц тем, что у них есть метаметоды, именно они и позволяют изменять поведение таблиц.

Метаметоды — это функции, которые вызываются при выполнении определенных операций над таблицами. Например, метаметод __add вызывается при сложении двух таблиц, а __index — при обращении к элементу таблицы.

Для того чтобы использовать метатаблицы нам нужно создать таблицу, в которой будут эти самые методы:

-- mt часто используется как сокращение от "metatable"
local mt = {
  __add = function(a, b)
    return a.value + b.value
  end,
}

Далее нам понадобятся специальные функции для установки и получения метатаблицы, они называются setmetatable и getmetatable соответственно:

  • setmetatable(table, metatable) — устанавливает метатаблицу для таблицы.
  • getmetatable(table) — возвращает метатаблицу для таблицы.

Мы можем использовать их с нашим mt для того чтобы добавить возможность складывать таблицы:

local mt = {
  __add = function(a, b)
    return a.value + b.value
  end,
}

local foo = { value = 10 }
local bar = { value = 20 }

setmetatable(foo, mt)
setmetatable(bar, mt)

local result = foo + bar
print(result) -- 30

В Lua доступны следующие метаметоды:

Метаметод Вызывается при Описание
__index t[k] (отсутствующий ключ) Настраивает доступ к полям таблицы (наследование прототипов).
__newindex t[k] = v (отсутствующий ключ) Настраивает присвоение значения отсутствующим ключам.
__add a + b Настраивает поведение при сложении.
__sub a - b Настраивает поведение при вычитании.
__mul a * b Настраивает поведение при умножении.
__div a / b Настраивает поведение при делении.
__mod a % b Настраивает поведение при вычислении остатка от деления.
__pow a ^ b Настраивает поведение при возведении в степень.
__unm -a Настраивает поведение при унарном минусе.
__concat a .. b Настраивает поведение при конкатенации.
__eq a == b Настраивает поведение при сравнении на равенство.
__lt a < b Настраивает поведение при сравнении на меньше.
__le a <= b Настраивает поведение при сравнении на меньше или равно.
__call t() Делает таблицу вызываемой как функцию.
__tostring tostring(t) или print(t) Настраивает преобразование таблицы в строку.
__len #t Настраивает поведение оператора длины.
__gc Сборка мусора Настраивает поведение таблиц при сборке мусора.
__metatable Получение метатаблицы Настраивает поведение таблиц при получении метатаблицы.
__pairs Итерация через pairs Настраивает поведение таблиц при итерации по таблице с помощью pairs.
__ipairs Итерация через ipairs Настраивает поведение таблиц при итерации по таблице с помощью ipairs.
__mode Использование слабых ссылок Настраивает поведение таблиц при использовании слабых ссылок.
__name Использование функции type Настраивает поведение таблиц при использовании функции type.

Метаметоды во многом похожи на прототипы в JavaScript, но в Lua они реализованы через метатаблицы. Это позволяет создавать более сложные структуры данных и реализовывать объектно-ориентированное программирование.

ООП

В Lua нет встроенной поддержки объектно-ориентированного программирования, но мы можем реализовать его с помощью метатаблиц и функций. Мы можем создать класс, который будет представлять собой таблицу, а методы будут представлять собой функции, которые работают с этой таблицей.

Давайте создадим класс Person, который будет представлять собой человека с именем и возрастом:

local Person = {}

-- Конструктор класса
function Person:new(name, age)
    local obj = { name = name, age = age }
    setmetatable(obj, self)
    self.__index = self
    return obj
end

-- Метод класса
function Person:greet()
    print("Hello, my name is " .. self.name .. " and I'm " .. self.age .. " years old.")
end

-- Создаем экземпляр класса
local person1 = Person:new("tokiory", 22)
person1:greet() -- Hello, my name is tokiory and I'm 22 years old.

Теперь мы можем более подробно разобрать данный сниппет кода:

local Person = {} 

-- Конструктор класса
function Person:new(name, age)
    local obj = { name = name, age = age }
    setmetatable(obj, self)
    self.__index = self
    return obj
end

-- Метод класса
function Person:greet()
    print("Hello, my name is " .. self.name .. " and I'm " .. self.age .. " years old.")
end

На первой строке мы создаем таблицу Person, которая будет представлять собой класс. В Lua классы реализуются через таблицы, а методы добавляются в них как функции.

local Person = {}

-- Конструктор класса
function Person:new(name, age)
    local obj = { name = name, age = age }
    setmetatable(obj, self)
    self.__index = self
    return obj
end

-- Метод класса
function Person:greet()
    print("Hello, my name is " .. self.name .. " and I'm " .. self.age .. " years old.")
end

Здесь мы определяем метод new, который будет выступать в роли конструктора класса. Он принимает два аргумента: name и age.

Нотация : в данном случае позволяет нам передавать объект (экземпляр класса) в качестве первого аргумента self, что позволяет нам обращаться к полям и методам класса, мы увидим как это будет использоваться чуть позднее.

local Person = {}

function Person:new(name, age)
    local obj = { name = name, age = age }
    setmetatable(obj, self)
    self.__index = self
    return obj
end

-- Метод класса
function Person:greet()
    print("Hello, my name is " .. self.name .. " and I'm " .. self.age .. " years old.")
end

Теперь мы создаем новую таблицу obj и инициализируем её полями name и age, которые принимают значения из аргументов функции.

Это нужно для того, чтобы при вызове нашей функции-конструктора мы все время отдавали пользователю новую таблицу, а не ссылались на старую.

local Person = {}

function Person:new(name, age)
    local obj = { name = name, age = age }
    setmetatable(obj, self) 
    self.__index = self
    return obj
end

-- Метод класса
function Person:greet()
    print("Hello, my name is " .. self.name .. " and I'm " .. self.age .. " years old.")
end

Для того чтобы наша таблица obj могла использовать методы класса, нам необходимо установить в качестве метатаблицы Person, это позволит нам использовать методы класса в экземпляре.

Напомню, что self в данном случае это Person, ибо в названии нашего конструктора мы использовали нотацию с :, где в первой части был Person.

local Person = {}

function Person:new(name, age)
    local obj = { name = name, age = age }
    setmetatable(obj, self)
    self.__index = self
    return obj
end

-- Метод класса
function Person:greet()
    print("Hello, my name is " .. self.name .. " and I'm " .. self.age .. " years old.")
end

Указываем, что при попытке доступа к отсутствующему ключу, Lua будет искать его в таблице Person.

Так как Person теперь является метатаблицей для obj, то все метаметоды (включая __index) будут применяться к obj, поэтому если мы попытаемся получить доступ к отсутствующему ключу, Lua будет искать его в Person.

1 / 0
Свайпайте для просмотра слайдов

Метатаблицы строк

После того, как мы выучили как работать с функциями и поняли как работают методы — самое время посмотреть как работать с методами уже встроенными в язык для работы со строками.

В Lua для работы со строками есть глобальная таблица string, от которой наследуются все строки (хотя, наверное, корректней говорить, что таблица string является метатаблицей для всех строк).

Вот какие функции реализованы в таблице string:

Функция Описание
string.byte Возвращает числовое значение ASCII символа в строке по указанной позиции. Если указаны начальная и конечная позиции, возвращает значения для всех символов в этом диапазоне.
string.char Преобразует один или несколько числовых кодов ASCII в строку.
string.find Ищет подстроку в строке и возвращает начальную и конечную позиции первого совпадения. Также может возвращать nil, если подстрока не найдена.
string.format Форматирует строку, используя синтаксис, похожий на printf в C.
string.gmatch Возвращает итератор, который позволяет проходить по всем совпадениям шаблона в строке.
string.gsub Заменяет все вхождения шаблона в строке на указанную подстроку или результат функции.
string.len Возвращает длину строки.
string.lower Преобразует все символы строки в нижний регистр.
string.match Ищет первое совпадение шаблона в строке и возвращает его.
string.rep Повторяет строку указанное количество раз и возвращает результат.
string.reverse Возвращает строку в обратном порядке.
string.sub Возвращает подстроку, начиная с указанной позиции и заканчивая другой указанной позицией.
string.upper Преобразует все символы строки в верхний регистр.
string.pack Упаковывает значения в бинарную строку в соответствии с указанным форматом.
string.unpack Распаковывает значения из бинарной строки в соответствии с указанным форматом.
string.packsize Возвращает размер строки, необходимой для хранения данных в указанном формате.

К примеру, давайте возьмем метод lower, который позволяет преобразовать строку в нижний регистр. Мы можем использовать его передав в него строку или же использовать оператор : для того, чтобы вызвать его как метод и передать саму строку в качестве аргумента self:

local foo = "HELLO"

print(string.lower(foo)) -- hello
print(foo:lower())       -- hello

Модули

В Lua модули представляют собой таблицы, которые содержат функции и данные, которые могут быть использованы в других частях программы. Модули позволяют организовать код и разделить его на логические части.

Для создания модуля нам нужно создать новый файл, в которым мы инициализируем таблицу и вернем её в конце файла:

mod.lua
lua
-- Общепринятной практикой считается называть модули буквой "M"
local M = {}

-- Инициализируем функции внутри модуля
M.hello = function()
    print("Hello, World!")
end

return M

Для того чтобы использовать модуль, нам нужно его импортировать с помощью функции require:

main.lua
lua
local mod = require("mod")
mod.hello() -- Hello, World!

Проверка на выполнение файла

В случае, если у вас есть модуль, который может являться исполняемым файлом, мы можем использоваться конструкцию if ... == nil для того, чтобы проверить, является ли файл исполняемым:

mod.lua
lua
local M = {}

-- Инициализируем функции внутри модуля
M.hello = function()
    print("Hello, World!")
end

-- Проверяем, является ли файл исполняемым
if ... == nil then
    M.hello()
end

return M

Встроенные модули

Ранее мы говорили о работе со строками и о том, что у строк есть метатаблица string. О работе с теми же числами мы не говорили ни слова, потому что в Lua нет встроенной метатаблицы для работы с числами, но есть встроенный модуль math, который позволяет работать с числами и математическими функциями.

Вот список функций, которые реализованы в модуле math:

Функция Описание
math.abs Возвращает абсолютное значение числа.
math.acos Возвращает арккосинус числа в радианах.
math.asin Возвращает арксинус числа в радианах.
math.atan Возвращает арктангенс числа в радианах.
math.atan2 Возвращает арктангенс координат y, x в радианах.
math.ceil Округляет число вверх до ближайшего целого.
math.cos Возвращает косинус угла в радианах.
math.cosh Возвращает гиперболический косинус числа.
math.deg Преобразует угол из радиан в градусы.
math.exp Возвращает экспоненту числа (e^x).
math.floor Округляет число вниз до ближайшего целого.
math.fmod Возвращает остаток от деления двух чисел.
math.huge Представляет бесконечность.
math.log Возвращает натуральный логарифм числа.
math.log10 Возвращает десятичный логарифм числа.
math.max Возвращает максимальное значение из списка аргументов.
math.min Возвращает минимальное значение из списка аргументов.
math.modf Возвращает дробную и целую части числа.
math.pi Константа, представляющая число π (пи).
math.pow Возводит число в степень (эквивалент x^y).
math.rad Преобразует угол из градусов в радианы.
math.random Возвращает случайное число.
math.randomseed Устанавливает начальное значение для генератора случайных чисел.
math.sin Возвращает синус угла в радианах.
math.sinh Возвращает гиперболический синус числа.
math.sqrt Возвращает квадратный корень числа.
math.tan Возвращает тангенс угла в радианах.
math.tanh Возвращает гиперболический тангенс числа.
math.type Возвращает тип числа ("integer" или "float").
math.ult Возвращает true, если первое число меньше второго беззнаковым сравнением.

Для того чтобы работать со встроенными модулями — нам не нужно их импортировать, так как они уже доступны в глобальной области видимости:

print(math.pow(2,3)) -- 2^3 = 8

Среди других встроенных модулей также есть следующие:

Модуль Описание
coroutine Предоставляет функции для работы с сопрограммами, такими как создание, возобновление и остановка сопрограмм.
io Предоставляет функции для работы с вводом-выводом, включая чтение и запись файлов.
os Предоставляет функции для работы с операционной системой, такие как получение текущего времени, выполнение команд и управление процессами.
package Предоставляет функции для работы с модулями и пакетами, включая загрузку и управление путями поиска модулей.
table Предоставляет функции для работы с таблицами, такие как сортировка, вставка и удаление элементов.
utf8 Предоставляет функции для работы с UTF-8 строками, включая проверку валидности и получение длины строки.
debug Предоставляет функции для отладки, такие как получение информации о вызовах функций и управление метатаблицами.

LuaRocks

LuaRocks — это менеджер пакетов для Lua, который позволяет устанавливать модули из репозиториев и управлять модулями и библиотеками, а также создавать свои собственные модули и делиться ими с другими пользователями. Он поддерживает различные версии Lua, что позволяет использовать его с различными проектами и библиотеками.

Для того чтобы установить LuaRocks, вам нужно скачать его с официального сайта и следовать инструкциям по установке. После установки вы сможете использовать команду luarocks в терминале для управления пакетами:

Корутины

Корутины — это легковесные потоки, которые позволяют выполнять несколько задач одновременно в одном потоке. Они позволяют приостанавливать выполнение функции и возобновлять его позже, что позволяет создавать асинхронные операции и обрабатывать события.

Для того чтобы использовать корутины в Lua есть отдельный встроенный модуль coroutine, который предоставляет функции для создания, управления и взаимодействия с корутинами:

Функция Назначение
coroutine.create(f) Создает корутину на основе функции f
coroutine.resume(co [, ...]) Запускает/возобновляет выполнение корутины
coroutine.yield(...) Приостанавливает выполнение
coroutine.status(co) Возвращает статус: "suspended", "running", "dead"
coroutine.wrap(f) Возвращает функцию, обертку над корутиной
coroutine.running() Возвращает текущую корутину (если есть)

Давайте создадим маленькую корутину, которая будет асинхронно отдавать числа:

local range = coroutine.create(function(n)
    for i = 1, n do
        coroutine.yield(i)
    end
end)

-- Запускаем корутину
coroutine.resume(range) -- 1
coroutine.resume(range) -- 2
coroutine.resume(range) -- 3
coroutine.resume(range) -- 4
--- ...

Мы также можем ожидать значения от корутины в цикле:

local range = coroutine.create(function(n)
    for i = 1, n do
        coroutine.yield(i)
    end
end)

-- Запускаем корутину
for i in range(5) do
  print(i)
end

Обработка ошибок

В Lua есть встроенные функции pcall, xpcall и error, которые позволяют обрабатывать ошибки и создавать их.

Для начала рассмотрим создание ошибок, это делается с помощью встроенной функции error, которая принимает любой тип данных в качестве аргумента и создает ошибку с этим значением. Она также может принимать второй аргумент, который указывает на уровень стека, на котором произошла ошибка.

error("Something went wrong!") -- Создаем ошибку
error({code = 408, message = "Server is deadass busy to work on your request"})

Функция pcall (сокращение от “protected call” или же “защищенный вызов”) — это функция, которая позволяет вызывать функции в защищенном режиме. Она принимает функцию и ее аргументы, выполняет ее и возвращает статус выполнения и результат. Если функция завершилась с ошибкой, то pcall вернет false и сообщение об ошибке.

local function divide(a, b)
    if b == 0 then
        error("division by zero")
    end
    return a / b
end

local status, result = pcall(divide, 10, 0) -- Делим на ноль
print(status) -- false
print(result) -- division by zero
--[[
  false
  main.lua:3: division by zero
]]

Также, у нас есть функция xpcall, которая работает точно также, как и pcall, однако, она позволяет нам передать функцию-обработчик ошибок, которая будет вызвана в случае возникновения ошибки. Она принимает функцию без аргументов, а также функцию-обработчик ошибок, в случае если нам нужно обработать функцию с аргументами, то мы можем просто обернуть ее в анонимную функцию:

local function divide(a, b)
    if b == 0 then
        error("division by zero")
    end
    return a / b
end

local function error_handler(err)
    print("WTF is this ->>> " .. err)
end

local status, result = xpcall(function() divide(10, 0) end, error_handler) -- Делим на ноль
print(status) -- false
print(result) -- division by zero

--[[
  WTF is this ->>> main.lua:3: division by zero
  false
  nil
]]