--- language: Lua filename: learnlua-ru.lua contributors: - ["Tyler Neylon", "http://tylerneylon.com/"] translators: - ["Max Solomonov", "https://vk.com/solomonovmaksim"] - ["Max Truhonin", "https://vk.com/maximmax42"] - ["Konstantin Gromyko", "https://vk.com/id0x1765d79"] - ["Stanislav Gromov", "https://vk.com/id156354391"] lang: ru-ru --- ```lua -- Два дефиса начинают однострочный комментарий. --[[ Добавление двух квадратных скобок делает комментарий многострочным. --]] -------------------------------------------------------------------------------- -- 1. Переменные, циклы и условия. -------------------------------------------------------------------------------- num = 42 -- Все числа имеют тип double. -- Не волнуйтесь, в 64-битных double 52 бита -- отведено под хранение целой части числа; -- точность не является проблемой для -- целочисленных значений, занимающих меньше 52 бит. s = 'walternate' -- Неизменные строки, как в Python. t = "Двойные кавычки также приветствуются" u = [[ Двойные квадратные скобки начинают и заканчивают многострочные значения.]] t = nil -- Удаляет определение переменной t; в Lua есть сборка мусора. -- Блоки обозначаются ключевыми словами, такими как do/end: while num < 50 do num = num + 1 -- Операторов ++ и += нет. end -- Ветвление "если": if num > 40 then print('больше 40') elseif s ~= 'walternate' then -- ~= обозначает "не равно". -- Проверка равенства это ==, как в Python; работает для строк. io.write('не больше 40\n') -- По умолчанию вывод в stdout. else -- По умолчанию переменные являются глобальными. thisIsGlobal = 5 -- Стиль CamelСase является общим. -- Как сделать переменную локальной: local line = io.read() -- Считывает введённую строку. -- Для конкатенации строк используется оператор .. : print('Зима пришла, ' .. line) end -- Неопределённые переменные возвращают nil. -- Этот пример не является ошибочным: foo = anUnknownVariable -- Теперь foo = nil. aBoolValue = false -- Только значения nil и false являются ложными; 0 и '' являются истинными! if not aBoolValue then print('это значение ложно') end -- Для 'or' и 'and' действует принцип "какой оператор дальше, -- тот и применяется". Это действует аналогично оператору a?b:c в C/js: ans = aBoolValue and 'yes' or 'no' --> 'no' karlSum = 0 for i = 1, 100 do -- Здесь указан диапазон, ограниченный с двух сторон. karlSum = karlSum + i end -- Используйте "100, 1, -1" как нисходящий диапазон: fredSum = 0 for j = 100, 1, -1 do fredSum = fredSum + j end -- В основном, диапазон устроен так: начало, конец[, шаг]. -- Другая конструкция цикла: repeat print('путь будущего') num = num - 1 until num == 0 -------------------------------------------------------------------------------- -- 2. Функции. -------------------------------------------------------------------------------- function fib(n) if n < 2 then return n end return fib(n - 2) + fib(n - 1) end -- Вложенные и анонимные функции являются нормой: function adder(x) -- Возвращаемая функция создаётся, когда вызывается функция adder, -- и запоминает значение переменной x: return function (y) return x + y end end a1 = adder(9) a2 = adder(36) print(a1(16)) --> 25 print(a2(64)) --> 100 -- Возвраты, вызовы функций и присвоения работают со списками, -- которые могут иметь разную длину. -- Лишние получатели принимают значение nil, а лишние значения игнорируются. x, y, z = 1, 2, 3, 4 -- Теперь x = 1, y = 2, z = 3, а 4 просто отбрасывается. function bar(a, b, c) print(a, b, c) return 4, 8, 15, 16, 23, 42 end x, y = bar('zaphod') --> выводит "zaphod nil nil" -- Теперь x = 4, y = 8, а значения 15..42 отбрасываются. -- Функции могут быть локальными и глобальными. Эти строки делают одно и то же: function f(x) return x * x end f = function (x) return x * x end -- Эти тоже: local function g(x) return math.sin(x) end local g = function(x) return math.sin(x) end -- Эквивалентно для local function g(x)..., однако ссылки на g -- в теле функции не будут работать, как ожидалось. local g; g = function (x) return math.sin(x) end -- 'local g' будет прототипом функции. -- Кстати, тригонометрические функции работают с радианами. -- Вызов функции с одним строковым параметром не требует круглых скобок: print 'hello' -- Работает без ошибок. -- Вызов функции с одним табличным параметром также -- не требует круглых скобок (про таблицы в след. части): print {} -- Тоже сработает. -------------------------------------------------------------------------------- -- 3. Таблицы. -------------------------------------------------------------------------------- -- Таблица = единственная составная структура данных в Lua; -- представляет собой ассоциативный массив. -- Подобно массивам в PHP или объектам в JS, они представляют собой -- хеш-таблицы, которые также можно использовать в качестве списков. -- Использование словарей: -- Литералы имеют ключ по умолчанию: t = {key1 = 'value1', key2 = false} -- Строковые ключи используются, как в точечной нотации в JS: print(t.key1) -- Печатает 'value1'. t.newKey = {} -- Добавляет новую пару ключ/значение. t.key2 = nil -- Удаляет key2 из таблицы. -- Литеральная нотация для любого значения ключа (кроме nil): u = {['@!#'] = 'qbert', [{}] = 1729, [6.28] = 'tau'} print(u[6.28]) -- пишет "tau" -- Ключ соответствует значению для чисел и строк, но при -- использовании таблицы в качестве ключа берётся её экземпляр. a = u['@!#'] -- Теперь a = 'qbert'. b = u[{}] -- Вы могли ожидать 1729, но получится nil: -- b = nil, т.к. ключ не будет найден. -- Это произойдёт потому, что за ключ мы использовали не тот же самый объект, -- который был использован для сохранения оригинального значения. -- Поэтому строки и числа удобнее использовать в качестве ключей. -- Вызов функции с одной таблицей в качестве аргумента -- не требует круглых скобок: function h(x) print(x.key1) end h{key1 = 'Sonmi~451'} -- Печатает 'Sonmi~451'. for key, val in pairs(u) do -- Цикл по таблице. print(key, val) end -- _G - это таблица со всеми глобалями. print(_G['_G'] == _G) -- Печатает 'true'. -- Использование таблиц, как списков / массивов: -- Список значений с неявно заданными целочисленными ключами: v = {'value1', 'value2', 1.21, 'gigawatts'} for i = 1, #v do -- #v - размер списка v. print(v[i]) -- Нумерация начинается с 1 !! end -- Список не является отдельным типом. v - всего лишь таблица -- с последовательными целочисленными ключами, воспринимаемая как список. -------------------------------------------------------------------------------- -- 3.1 Метатаблицы и метаметоды. -------------------------------------------------------------------------------- -- Таблицу можно связать с метатаблицей, задав ей поведение, как при -- перегрузке операторов. Позже мы увидим, что метатаблицы поддерживают -- поведение, как в js-прототипах. f1 = {a = 1, b = 2} -- Представляет дробь a/b. f2 = {a = 2, b = 3} -- Это не сработает: -- s = f1 + f2 metafraction = {} function metafraction.__add(f1, f2) local sum = {} sum.b = f1.b * f2.b sum.a = f1.a * f2.b + f2.a * f1.b return sum end setmetatable(f1, metafraction) setmetatable(f2, metafraction) s = f1 + f2 -- вызвать __add(f1, f2) на метатаблице от f1 -- f1, f2 не имеют ключа для своих метатаблиц в отличии от прототипов в js, -- нужно получить его через getmetatable(f1). Метатаблица - обычная таблица -- поэтому с ключами, известными для Lua (например, __add). -- Но следущая строка будет ошибочной т.к в s нет метатаблицы: -- t = s + s -- Похожий на классы подход, приведенный ниже, поможет это исправить. -- __index перегружает в метатаблице просмотр через точку: defaultFavs = {animal = 'gru', food = 'donuts'} myFavs = {food = 'pizza'} setmetatable(myFavs, {__index = defaultFavs}) eatenBy = myFavs.animal -- работает! спасибо, мета-таблица. -------------------------------------------------------------------------------- -- При неудаче прямой табличный поиск попытается использовать -- значение __index в метатаблице, причём это рекурсивно. -- Значение __index также может быть функцией -- function(tbl, key) для настраиваемого поиска. -- Значения типа __index, __add, ... называются метаметодами. -- Ниже приведён полный список метаметодов. -- __add(a, b) для a + b -- __sub(a, b) для a - b -- __mul(a, b) для a * b -- __div(a, b) для a / b -- __mod(a, b) для a % b -- __pow(a, b) для a ^ b -- __unm(a) для -a -- __concat(a, b) для a .. b -- __len(a) для #a -- __eq(a, b) для a == b -- __lt(a, b) для a < b -- __le(a, b) для a <= b -- __index(a, b) <функция или таблица> для a.b -- __newindex(a, b, c) для a.b = c -- __call(a, ...) для a(...) -------------------------------------------------------------------------------- -- 3.2 Классоподобные таблицы и наследование. -------------------------------------------------------------------------------- -- В Lua нет поддержки классов на уровне языка, -- однако существуют разные способы их создания с помощью -- таблиц и метатаблиц. -- Ниже приведён один из таких способов. Dog = {} -- 1. function Dog:new() -- 2. local newObj = {sound = 'woof'} -- 3. self.__index = self -- 4. return setmetatable(newObj, self) -- 5. end function Dog:makeSound() -- 6. print('I say ' .. self.sound) end mrDog = Dog:new() -- 7. mrDog:makeSound() -- 'I say woof' -- 8. -- 1. Dog похоже на класс, но на самом деле это таблица. -- 2. "function tablename:fn(...)" - то же самое, что и -- "function tablename.fn(self, ...)", просто : добавляет первый аргумент -- перед собой. См. пункты 7 и 8, чтобы понять, как self получает значение. -- 3. newObj - это экземпляр класса Dog. -- 4. "self" - экземпляр класса. Зачастую self = Dog, но с помощью наследования -- это можно изменить. newObj получит свои функции, когда мы установим -- метатаблицу для newObj и __index для self на саму себя. -- 5. Напоминание: setmetatable возвращает первый аргумент. -- 6. : работает, как в пункте 2, но в этот раз мы ожидаем, -- что self будет экземпляром, а не классом. -- 7. То же самое, что и Dog.new(Dog), поэтому self = Dog в new(). -- 8. То же самое, что mrDog.makeSound(mrDog); self = mrDog. -------------------------------------------------------------------------------- -- Пример наследования: LoudDog = Dog:new() -- 1. function LoudDog:makeSound() local s = self.sound .. ' ' -- 2. print(s .. s .. s) end seymour = LoudDog:new() -- 3. seymour:makeSound() -- 'woof woof woof' -- 4. -------------------------------------------------------------------------------- -- 1. LoudDog получит методы и переменные класса Dog. -- 2. В self будет ключ 'sound' из new(), см. пункт 3. -- 3. То же самое, что и "LoudDog.new(LoudDog)", конвертированное -- в "Dog.new(LoudDog)", поскольку в LoudDog нет ключа 'new', -- но в его метатаблице есть "__index = Dog". -- Результат: Метатаблицей для seymour стала LoudDog, -- а "LoudDog.__index = Dog". Поэтому seymour.key будет равно -- seymour.key, LoudDog.key, Dog.key, в зависимости от того, -- какая таблица будет первой с заданным ключом. -- 4. Ключ 'makeSound' находится в LoudDog; -- то же самое, что и "LoudDog.makeSound(seymour)". -- При необходимости функция new() в подклассе -- может быть похожа на аналог в базовом классе. function LoudDog:new() local newObj = {} -- установить newObj self.__index = self return setmetatable(newObj, self) end -------------------------------------------------------------------------------- -- 4. Модули. -------------------------------------------------------------------------------- --[[ Я закомментировал этот раздел, чтобы остальная часть скрипта осталась -- работоспособной. ``` ```lua -- Предположим, файл mod.lua будет выглядеть так: local M = {} local function sayMyName() print('Hrunkner') end function M.sayHello() print('Привет, ') sayMyName() end return M -- Другой файл может использовать функциональность mod.lua: local mod = require('mod') -- Запустим файл mod.lua. -- require - стандартный способ подключения модулей. -- require ведёт себя так: (если не кэшировано, см. ниже) local mod = (function () <содержимое mod.lua> end)() -- Файл mod.lua воспринимается, как тело функции, поэтому -- все локальные переменные и функции внутри него не видны за его пределами. -- Это работает, так как здесь mod = M в mod.lua: mod.sayHello() -- Выведет "Привет, Hrunkner". -- Это будет ошибочным; sayMyName доступна только в mod.lua: mod.sayMyName() -- ошибка -- Значения, возвращаемые require, кэшируются, -- поэтому содержимое файла выполняется только 1 раз, -- даже если он подключается с помощью require много раз. -- Предположим, mod2.lua содержит "print('Hi!')". local a = require('mod2') -- Выведет "Hi!" local b = require('mod2') -- Ничего не выведет; a=b. -- dofile, в отличии от require, работает без кэширования: dofile('mod2') --> Hi! dofile('mod2') --> Hi! (запустится снова) -- loadfile загружает файл, но не запускает его. f = loadfile('mod2') -- Вызов f() запустит содержимое mod2.lua. -- loadstring - это loadfile для строк. g = loadstring('print(343)') -- Вернет функцию. g() -- Напишет 343. --]] ``` ## Примечание (от автора) Мне было интересно изучить Lua, чтобы делать игры при помощи [игрового движка LÖVE](http://love2d.org/). Я начинал с [BlackBulletIV's Lua for programmers](http://nova-fusion.com/2012/08/27/lua-for-programmers-part-1/). Затем я прочитал официальную [Документацию по Lua](http://www.lua.org/pil/contents.html). Также может быть полезной [Краткая справка по Lua](http://lua-users.org/files/wiki_insecure/users/thomasl/luarefv51.pdf) на lua-users.org. Ещё из основных тем не охвачены стандартные библиотеки: * [библиотека `string`](http://lua-users.org/wiki/StringLibraryTutorial) * [библиотека `table`](http://lua-users.org/wiki/TableLibraryTutorial) * [библиотека `math`](http://lua-users.org/wiki/MathLibraryTutorial) * [библиотека `io`](http://lua-users.org/wiki/IoLibraryTutorial) * [библиотека `os`](http://lua-users.org/wiki/OsLibraryTutorial) Кстати, весь файл написан на Lua; сохраните его как learn.lua и запустите при помощи `lua learn.lua` Изначально эта статья была написана для tylerneylon.com. Также она доступна как [GitHub gist](https://gist.github.com/tylerneylon/5853042). Удачи с Lua!