Как узнать какая функция была вызвана lua. Функции математической библиотеки Lua. Операторы break и return в Lua

Lua предлагает высокоуровневую абстракцию без потери связи с аппаратурой

В то время как интерпретируемые языки программирования, такие как Perl, Python, PHP и Ruby, пользуются все большей популярностью для Web-приложений (и уже давно предпочитаются для автоматизации задач по системному администрированию), компилируемые языки программирования, такие как C и C++, по-прежнему необходимы. Производительность компилируемых языков программирования остается несравнимой (она уступает только производительности ручного ассемблирования), поэтому некоторое программное обеспечение (включая операционные системы и драйверы устройств) может быть реализована эффективно только при использовании компилируемого кода. Действительно, всегда, когда программное и аппаратное обеспечение нужно плавно связать между собой, программисты инстинктивно приходят к компилятору C: C достаточно примитивен для доступа к "голому железу" (то есть, для использования особенностей какой-либо части аппаратного обеспечения) и, в то же время, достаточно выразителен для описания некоторых высокоуровневых программных конструкций, таких как структуры, циклы, именованные переменные и области видимости.

Однако языки сценариев тоже имеют четкие преимущества. Например, после успешного переноса интерпретатора языка на другую платформу подавляющее большинство написанных на этом языке сценариев работает на новой платформе без изменений, не имея зависимостей, таких как системные библиотеки функций (представьте множество DLL-файлов операционной системы Microsoft® Windows® или множество libcs на UNIX® и Linux®). Кроме того, языки сценариев обычно предлагают высокоуровневые программные конструкции и удобные операции, которые программистам нужны для повышения продуктивности и скорости разработки. Более того, программисты, использующие язык сценариев, могут работать быстрее, поскольку этапы компиляции и компоновки не нужны. В сравнении с С и его родственниками цикл "кодирование, компоновки, связывание, запуск" сокращается до ускоренного "написание, запуск".

Новшества в Lua

Как и любой язык сценариев, Lua имеет свои особенности:

  • Типы в Lua . В Lua значения имеют тип, но переменные типизируются динамически. Типы nil , boolean , number и string работают так, как вы могли бы ожидать.
    • Nil - это тип специального значения nil ; используется для представления отсутствия значения.
    • Boolean - это тип констант true и false (Nil тоже представляет значение false , а любое не nil значение представляет true).
    • Все числа в Lua имеют тип doubles (но вы можете легко создать код для реализации других числовых типов).
    • string - это неизменяемый массив для символов (следовательно, для добавления к строке вы должны сделать ее копию).
  • Типы table , function и thread являются ссылками. Каждый такой тип может быть назначен переменной, передаваемой в качестве аргумента, или возвращаемой из функции. Ниже приведен пример сохранения функции:

    Пример анонимной функции, -- возвращаемой как значение -- см. http://www.tecgraf.puc-rio.br/~lhf/ftp/doc/hopl.pdf function add(x) return function (y) return (x + y) end end f = add(2) print(type(f), f(10)) function 12

  • Потоки в Lua . Поток - это сопрограмма, создаваемая вызовом встроенной функции coroutine.create(f) , где f - это функция Lua. Потоки не запускаются при создании; они запускаются позже при помощи функции coroutine.resume(t) , где t - это поток. Каждая сопрограмма может время от времени отдавать процессор другим сопрограммам при помощи функции coroutine.yield() .
  • Выражения присваивания . Lua разрешает множественные присваивания, и выражения сначала вычисляются, а затем присваиваются. Например, результат выражений

    I = 3 a = {1, 3, 5, 7, 9} i, a[i], a, b = i+1, a, a[i] print (i, a, a, b, I)
    равен 4 7 5 nil nil . Если список переменных больше, чем список значений, лишним переменным присваивается значение nil ; поэтому b равно nil . Если значений больше, чем переменных, лишние значения просто игнорируются. В Lua названия переменных зависят от регистра символов, что объясняет, почему переменная I равна nil .

  • Порции (chunks) . Порцией называется любая последовательность Lua-операторов. Порция может быть записана в файл или в строку в Lua-программе. Каждая порция выполняется как тело анонимной функции. Следовательно, порция может определять локальные переменные и возвращать значения.
  • Дополнительные интересные возможности . Lua имеет сборщик мусора "отметь и выкинь". В Lua 5.1 сборщик мусора работает в инкрементном режиме. Lua имеет полное лексическое замыкание (как Scheme, но не как Python). Кроме того, Lua имеет надежную семантику последовательных вызовов (tail call) (опять же, как Scheme, но не как Python).

Большее количество примеров Lua-кода приведено в руководстве "Программирование в Lua " и в wiki Lua-пользователей (ссылки приведены в разделе " ").

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

Беря все лучшее из обоих миров

Что, если бы вы могли взять лучшее из обоих миров: производительность работы с "голым железом" и высокоуровневые, мощные абстракции? Более того, если бы вы могли оптимизировать алгоритмы и функции, зависящие от системы и требующие много процессорного времени, так же как и отдельную логику, не зависящую от системы и очень чувствительную к изменениям требований?

Баланс требований для высокопроизводительного кода и высокоуровневого программирования является сутью Lua, встраиваемого языка программирования. Приложения, включающие Lua, представляют собой комбинацию компилируемого кода и Lua-сценариев. Компилируемый код может при необходимости заняться железом, и, в то же время, может вызывать Lua-сценарии для обработки сложных данных. И поскольку Lua-сценарии отделены от компилируемого кода, вы можете изменять сценарии независимо от него. С Lua цикл разработки более похож на "Кодирование, компоновка, запуск, создание сценариев, создание сценариев, создание сценариев …".

Например, на странице "Uses" Web-сайта Lua (см. раздел " ") перечислены некоторые компьютерные игры для массового рынка, включая World of Warcraft и Defender (версия классической аркады для бытовых консолей), которые интегрируют Lua для запуска всего, начиная с пользовательского интерфейса и заканчивая искусственным интеллектом противника. Другие приложения Lua включают в себя механизмы расширения для популярного инструментального средства обновления Linux-приложений apt-rpm и механизмы управления чемпионатом Robocup 2000 "Сумасшедший Иван". На этой странице есть много хвалебных отзывов о маленьком размере и отличной производительности Lua.

Начало работы с Lua

Lua версии 5.0.2 на момент написания данной статьи была текущей версией (недавно появилась версия 5.1). Вы можете загрузить исходный код Lua с lua.org, а можете найти различные предварительно откомпилированные двоичные файлы на wiki Lua-пользователей (ссылки приведены в разделе " "). Полный код ядра Lua 5.0.2, включая стандартные библиотеки и Lua-компилятор, по размерам не превышает 200KB.

Если вы работаете на Debian Linux, то можете быстро и просто установить Lua 5.0 при помощи следующей команды

# apt-get install lua50

с правами суперпользователя. Все приведенные здесь примеры запускались на Debian Linux "Sarge" с использованием Lua 5.0.2 и ядра Linux 2.4.27-2-686.

После установки Lua на вашей системе попробуйте автономный Lua-интерпретатор. Все Lua-приложения должны быть встроены в базовое приложение. Интерпретатор - это просто специальный тип базового приложения, используемого для разработки и отладки. Создайте файл factorial.lua и введите в него следующие строки:

-- определяет функцию факториала function fact (n) if n == 0 then return 1 else return n * fact(n-1) end end print("enter a number:") a = io.read("*number") print(fact(a))

Код в factorial.lua (точнее, любая последовательность Lua-операторов) называется порцией (chunk), как было описано выше в разделе " ". Для запуска созданной вами порции выполните команду lua factorial.lua:

$ lua factorial.lua enter a number: 10 3628800

Или, как в других языках сценариев, вы можете добавить строку со знаками (#!) ("shebang") в начало сценария, делая сценарий исполняемым, а затем запустить файл как автономную команду:

$ (echo "#! /usr/bin/lua"; cat factorial.lua) > factorial $ chmod u+x factorial $ ./factorial enter a number: 4 24

Язык Lua

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

В Lua тип имеют только значения , а переменные типизируются динамически. В Lua есть восемь фундаментальных типов (или значений): nil , boolean , number , string , function , thread , table и userdata . Первые шесть типов говорят сами за себя (исключения приведены в разделе " "); два последних требуют пояснения.

Таблицы в Lua

Таблицы - это универсальная структура данных в Lua. Более того, таблицы - это единственная структура данных в Lua. Вы можете использовать таблицу как массив, словарь (называемый также хеш-таблицей или ассоциативным массивом ), дерево, запись и т.д.

В отличие от других языков программирования, содержимое таблицы в Lua не обязательно должно быть однородным: таблица может включать любые типы и может содержать смесь элементов, подобных массиву, и элементов, подобных словарю. Кроме того, любое Lua-значение (в том числе, функция или другая таблица) может служить ключом элемента словаря.

Для исследования таблиц запустите Lua-интерпретатор и введите строки, показанные жирным шрифтом в листинге 1.

Листинг 1. Экспериментируя с таблицами Lua
$ lua > -- создать пустую таблицу и добавить несколько элементов > t1 = {} > t1 = "moustache" > t1 = 3 > t1["brothers"] = true > -- создать таблицу и определить элементы (употребляется чаще) > all at once > t2 = { = "groucho", = "chico", = "harpo"} > t3 = { = t2, accent = t2, horn = t2} > t4 = {} > t4 = "the marx brothers" > t5 = {characters = t2, marks = t3} > t6 = {["a night at the opera"] = "classic"} > -- создать ссылку и строку > i = t3 > s = "a night at the opera" > -- индексами могут быть любые Lua-значения > print(t1, t4, t6[s]) moustache the marx brothers classic > -- фраза table.string эквивалентна фразе table["string"] > print(t3.horn, t3["horn"]) harpo harpo > -- индексы могут быть также "многомерными" > print (t5["marks"]["horn"], t5.marks.horn) harpo harpo > -- i указывает на то же значение, что и t3 > = t4[i] the marx brothers > -- несуществующие индексы возвращают значения nil > print(t1, t2, t5.films) nil nil nil > -- даже функция может быть ключом > t = {} > function t.add(i,j) >> return(i+j) >> end > print(t.add(1,2)) 3 > print(t["add"](1,2)) 3 > -- и другой вариант функции в качестве ключа > t = {} > function v(x) >> print(x) >> end > t[v] = "The Big Store" > for key,value in t do key(value) end The Big Store

Как вы могли ожидать, Lua также предоставляет несколько функций-итераторов для обработки таблиц. Функции предоставляет глобальная переменная table (да, Lua-пакеты - это тоже просто таблицы). Некоторые функции, например table.foreachi() , ожидают непрерывный диапазон целых ключей, начиная с 1 (цифра один):

> table.foreachi(t1, print) 1 moustache 2 3

Другие, например table.foreach() , выполняют итерацию по всей таблице:

> table.foreach(t2,print) 1 groucho 3 chico 5 harpo > table.foreach(t1,print) 1 moustache 2 3 brothers true

Хотя некоторые итераторы оптимизированы для целых индексов, все они просто обрабатывают пары (ключ, значение).

Ради интереса создайте таблицу t с элементами {2, 4, 6, language="Lua", version="5", 8, 10, 12, web="www.lua.org"} и выполните команды table.foreach(t, print) и table.foreachi(t, print) .

Userdata

Поскольку Lua предназначен для встраивания в базовое приложение, написанное на таких языках, как, например, C или C++, для взаимодействия с базовым приложением данные должны совместно использоваться средой C и Lua. Как указано в "Справочном руководстве по Lua 5.0 ", тип userdata позволяет "произвольным C-данным храниться в Lua-переменных". Вы можете рассматривать тип userdata как массив байтов - байтов, которые могут представлять указатель, структуру или файл в базовом приложении.

Содержимое userdata происходит от C, поэтому оно не может быть модифицировано в Lua. Естественно, поскольку userdata происходит от C, в Lua не существует предопределенных операций для userdata. Однако вы можете создать операции, которые работают с userdata , используя еще один механизм Lua, называемый мета-таблицами (metatables).

Мета-таблицы

Из-за такой гибкости типов table и userdata Lua разрешает перегружать операции для объектов каждого из этих типов (вы не можете перегружать шесть остальных типов). Мета-таблица - это (обычная) Lua-таблица, которая отображает стандартные операции в предоставляемые вами пользовательские функции. Ключи мета-таблицы называются событиями (event); значения (другими словами, функции) называются мета-методами (metamethod).

Функции setmetatable() и getmetatable() изменяют и запрашивают мета-таблицу объекта соответственно. Каждый объект table и userdata может иметь свою собственную мета-таблицу.

Например, одним из событий является __add (для добавления). Можете ли вы определить, что делает следующая порция?

-- Перегрузить операцию add -- для конкатенации строк -- mt = {} function String(string) return setmetatable({value = string or ""}, mt) end -- Первый операнд - это String table -- Второй операнд - это string -- .. - это операция конкатенации в Lua -- function mt.__add(a, b) return String(a.value..b) end s = String("Hello") print((s + " There " + " World!").value)

Эта порция отображает следующий текст:

Hello There World!

Функция function String() принимает строку (string), заключает ее в таблицу ({value = s or ""}) и назначает мета-таблицу mt этой таблице. Функция mt.__add() является мета-методом, добавляющим строку b к строке, находящейся в a.value b раз. Строка print((s + " There " + " World!").value) активизирует мета-метод дважды.

Index - это еще одно событие. Мета-метод для __index вызывается всегда, когда ключ в таблице не существует. Вот пример, который запоминает ("memoizes") значение функции:

-- код, любезно предоставленный Рики Лэйком (Rici Lake), [email protected] function Memoize(func, t) return setmetatable(t or {}, {__index = function(t, k) local v = func(k); t[k] = v; return v; end }) end COLORS = {"red", "blue", "green", "yellow", "black"} color = Memoize(function(node) return COLORS end)

Поместите этот код в Lua-интерпретатор и введите print(color, color, color) . Вы должны увидеть что-то подобное blue black blue .

Этот код, получающий ключ и узел, ищет цвет узла. Если он не существует, код присваивает узлу новый, выбранный случайно цвет. В противном случае возвращается цвет, назначенный узлу. В первом случае мета-метод __index выполняется один раз для назначения цвета. В последнем случае выполняется простой и быстрый поиск в хеш-таблице.

Язык Lua предлагает много мощных функциональных возможностей, и все они хорошо документированы. Но всегда, когда вы столкнетесь с проблемами или захотите пообщаться с мастером, обратитесь за поддержкой к энтузиастам - IRC-канал Lua Users Chat Room (см. раздел " ").

Встроить и расширить

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

Сердцем симбиотического взаимодействия между Lua и его базовым языком является виртуальный стек . Виртуальный стек (как и реальный) является структурой данных "последний вошел - первый вышел" (last in-first out - LIFO), которая временно сохраняет аргументы функции и ее результаты. Для вызова из Lua базового языка (и наоборот) вызывающая сторона помещает значения в стек и вызывает целевую функцию; принимающая сторона достает аргументы из стека (конечно же, проверяя тип и значение каждого аргумента), обрабатывает данные и помещает в стек результаты. Когда управление возвращается вызывающей стороне, она извлекает значения из стека.

Фактически, все С-интерфейсы прикладного программирования (API) для Lua-операций работают через стек. Стек может хранить любое Lua-значение; однако тип значения должен быть известен как вызывающей стороне, так и вызываемой, а конкретные функции помещают в стек и извлекают из него каждый тип (например, lua_pushnil() и lua_pushnumber()).

В листинге 2 показана простая C-программа (взятая из главы 24 книги "Программирование в Lua ", ссылка на которую приведена в разделе " "), реализующая минимальный, но функциональный Lua-интерпретатор.

Листинг 2. Простой Lua-интерпретатор
1 #include 2 #include 3 #include 4 #include 5 6 int main (void) { 7 char buff; 8 int error; 9 lua_State *L = lua_open(); /* открывает Lua */ 10 luaopen_base(L); /* открывает основную библиотеку */ 11 luaopen_table(L); /* открывает библиотеку table */ 12 luaopen_io(L); /* открывает библиотеку I/O */ 13 luaopen_string(L); /* открывает библиотеку string */ 14 luaopen_math(L); /* открывает библиотеку math */ 15 16 while (fgets(buff, sizeof(buff), stdin) != NULL) { 17 error = luaL_loadbuffer(L, buff, strlen(buff), "line") || 18 lua_pcall(L, 0, 0, 0); 19 if (error) { 20 fprintf(stderr, "%s", lua_tostring(L, -1)); 21 lua_pop(L, 1); /* извлечь сообщение об ошибке из стека */ 22 } 23 } 24 25 lua_close(L); 26 return 0; 27 }

Строки с 2 по 4 включают стандартные Lua-функции, несколько удобных функций, используемых во всех Lua-библиотеках, и функции для открытия библиотек, соответственно. Строка 9 создает Lua-структуру . Все структуры сначала пусты; вы добавляете библиотеки или функции к структуре при помощи luaopen_...() , как показано в строках с 10 по 14.

В строке 17 luaL_loadbuffer() принимает входную информацию с stdin в виде порции и компилирует ее, помещая порцию в виртуальный стек. Строка 18 извлекает порцию из стека и выполняет ее. Если во время исполнения возникает ошибка, Lua-строка помещается в стек. Строка 20 обращается к вершине стека (вершина стека имеет индекс -1) как к Lua-строке, распечатывает сообщение и удаляет значение из стека.

Используя C API, ваше приложение может также "достать" информацию из Lua-структуры. Следующий фрагмент кода извлекает две глобальные переменные из Lua-структуры:

.. if (luaL_loadfile(L, filename) || lua_pcall(L, 0, 0, 0)) error(L, "cannot run configuration file: %s", lua_tostring(L, -1)); lua_getglobal(L, "width"); lua_getglobal(L, "height"); .. width = (int) lua_tonumber(L, -2); height = (int) lua_tonumber(L, -1); ..

Опять же, обратите внимание на то, что передачу разрешает стек. Вызов любой Lua-функции из C аналогичен следующему коду: извлечь функцию при помощи lua_getglobal() , поместить аргументы, выполнить lua_pcall() и обработать результаты. Если Lua-функция возвращает n значений, первое значение находится по индексу -n в стеке, а последнее - по индексу -1 .

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

Lua великолепен

Lua - это чрезвычайно легкий в использовании язык, но его простой синтаксис маскирует его мощь: язык поддерживает объекты (аналогичные объектам Perl), мета-таблицы делают его тип table абсолютно гибким, а C API разрешает отличную интеграцию и расширение сценариев и базового языка. Lua может использоваться совместно с языками C, C++, C#, Java™ и Python.

Перед созданием еще одного формата конфигурационного файла или ресурса (и еще одного синтаксического анализатора для него) попробуйте Lua. Язык Lua (так же как и его сообщество) надежен, изобретателен и готов прийти на помощь.

Вот тут, наконец-то, пример полноценной прокси, с отслеживанием действий.

Function proxy() local real_table = {} local logger = {} local metatable = {} -- Забираем значения из настоящей таблицы и логируем их function metatable:__index(key) local value = rawget(real_table, key) table.insert(logger, "Get key "..tostring(key).." is ".. tostring(value)) return value end -- Подставляем значения в реальную таблицу, с произвольными действиями function metatable:__newindex(key, value) table.insert(logger, "Set key "..tostring(key).." as "..tostring(value)) rawset(real_table, key, value) end return setmetatable({}, metatable), logger end prox, log = proxy() prox.a = 10 prox.a = 20 print("prox.a", prox.a) --> 20 print("log", "\n"..table.concat(log, "\n")) --> Set key a as 10 --> Set key a as 20 --> Get key a, is 20
На выходе таблица, которая логирует её использование. В данном случае, таблица-прокси всегда пуста, в ней нет никаких ключей, поэтому __newindex будет вызываться каждый раз.

Таблицы временных объектов

Время от времени, могут понадобиться временные объекты, которые существуют некоторое время, но при нехватке памяти - освобождают занимаемое пространство. Данный пример потребует наличия библиотеки Luasec (https-запросы), хотя с тем же успехом можно использовать Luasocket , только без https.

Page = {} page.https = require"ssl.https" page.cache = setmetatable({}, {__mode = "v"}) -- Метатаблица для отдельных страничек page._meta = {} function page._meta:__tostring() return "Page: ["..self.url.. "]: ".. self.status end setmetatable(page, page) function page:__call(url) return self.cache or self:request(url) end function page:request(url) local page = setmetatable({}, self._meta) page.url = url page.data, page.status, page.error, page.hate = self.https.request(url) print(page.data, page.status, page.error, page.hate) self.cache = page return page end -- Запрашиваем, например, Яндекс. p = page("https://yandex.ru") print("p", p) --> Page: : 200 print("p.status", p.status) --> 200 -- И он болтается в кеше, -- при последующих аналогичных запросах - будет извлекаться оттуда. print("page.cache[...]", page.cache["https://yandex.ru"]) --> Page: : 200 -- Он остаётся после сборки мусора, потому что остаётся сильная ссылка "p". collectgarbage() print("page.cache[...]", page.cache["https://yandex.ru"]) --> Page: : 200 p = nil collectgarbage() -- Но после удаления ссылки - её больше нет, страничка больше не нужна. print("page.cache[...]", page.cache["https://yandex.ru"]) --> Nil

Пока всё

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

Для желающих задать кучу вопросов - оставляю ссылку на чатик в тележке .

Теги: Добавить метки

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

UPD : заголовок изменён на более логичный, исправлены некоторые ошибки, из-за которых статья может быть понята неверно.

=== Урок №4 ===

В прошлый раз мы, помимо всего прочего, узнали о довольно большом количестве функций, упрощающих многие расчётные задачи. Функция - действительно мощный инструмент программирования, простой и удобный в использовании: вы просто передаёте ей параметр и получаете результат. В случае с математическими функциями (такими, как math.cos, math.sqrt), желаемым результатом является число, которое можно использовать в вычислениях; в случае с функцией print, давно и прочно засевшей в наших головах, желаемый результат - текст, появляющийся в окне консоли. Так как print не возвращает никакого значения (или, что то же самое, любой вызов print возвращает nil ), но что-то полезное тем не менее делает, то говорят, что print вызывают ради побочного действия .

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

Типичный синтаксис определения функции:

200?"200px":""+(this.scrollHeight+5)+"px");">
function <название> (<список_параметров>)
<инструкции>
end

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

200?"200px":""+(this.scrollHeight+5)+"px");">
-- func.lua --

Function hello()
print "hello"
print "hi"
end

Теперь вызывайте функцию hello хоть 50 раз подряд
hello()
hello()
hello()

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

А таким образом определяется функция с параметрами:

200?"200px":""+(this.scrollHeight+5)+"px");">
-- func2.lua --

Function printmany(str, times)
for i = 1, times do
print(str)
end
end

Print("bug", 6)
print("goat", 2)

Функция printmany принимает строку, заданную первым параметром, и выводит её столько раз, сколько указано во втором параметре.
Таким образом, программа выведет 6 раз строку "bug" и 2 раза строку "goat", и если вы внимательно читали предыдущие уроки, вам должно быть понятно, почему. Понимать работу printmany следует так: при вызове print("bug", 6) внутри функции автоматически создаются переменные string = "bug" и times = 6 , аналогичное происходит и при вызове print("goat", 2) .

Заметьте, что в Lua нет никакой проверки типов: то есть, вы, в принципе, можете передать функции в качестве параметра любое значение, а не только то, которое она от вас вроде как ждёт. Это делает допустимыми следующие вызовы в предыдущей программе:

200?"200px":""+(this.scrollHeight+5)+"px");">
printmany(78, 1) -- 1 раз выводит число 78
printmany(nil, 3) -- 3 раза выводит nil
printmany(false, 8) -- 8 раз выводит false

Однако такой номер не пройдёт:

200?"200px":""+(this.scrollHeight+5)+"px");">
printmany("error", "seven")

В таком случае интерпретатор выдаст ошибку: он не сможет в цикле посчитать от одного до строки "seven". Увы, компьютеры порой так глупы! Что я хочу сказать: будьте внимательны, когда передаёте функции параметры, вместо вас за ними следить некому.

Число параметров в создаваемой функции может быть любым: хоть ноль, хоть два, хоть три, хоть три тысячи - впрочем, надеюсь, вам не захочется создавать функцию с тремя тысячами параметрами. Возможно создать функцию, которая принимала бы произвольное число параметров (наподобие print: вы можете передать ей столько значений, сколько вам вздумается), но об этом будет рассказано в одном из более поздних уроков: пока обойдёмся фиксированным.

Функции, описанные нами до этого, не возвращали никаких значений. Убедитесь сами:

200?"200px":""+(this.scrollHeight+5)+"px");">
value1 = printmany("cat", 1)
value2 = math.cos(math.pi)
print(value1, value2)
-- Выведет: nil -1

Вернуть из функции значение можно с помощью инструкции return . Делается примерно так:

200?"200px":""+(this.scrollHeight+5)+"px");">
-- vecfun.lua--

Функция возводит заданное число в квадрат
function square(x)
return x^2
end

Print(square(11))
-- Выведет 121

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

200?"200px":""+(this.scrollHeight+5)+"px");">
-- nothing.lua --

Этот код не делает вообще ничего!
-- return в данном случае, конечно,
-- можно просто убрать.

Function nothing()
return
end

"Голый" return может использоваться, например, если вы хотите раньше времени завершить исполнение функции, подобно тому, как break используется для прерывания цикла. Можно также написать return nil , и смысл у этой инструкции будет такой же, как у простого return .

Функция не обязательно должна иметь один-единтсвенный return - вполне возможно создать несколько ветвей исполнения, каждая из которых возвращает своё значение. Например:

200?"200px":""+(this.scrollHeight+5)+"px");">
-- sign.lua --

Function sign(x)
if x > 0 then
return 1
elseif x < 0 then
return -1
else
return 0
end
end

Print(sign(9), sign(0), sign(-144))
-- Выведет: 1 0 -1

Вы наверняка уже догадались, что функция может возвращать не только числа, но и любые другие значения, которые поддерживаются языком Lua: например, логические значения (true и false ), строки.

200?"200px":""+(this.scrollHeight+5)+"px");">
-- logicfun.lua --

Та самая функция xor из одного из упражнений предыдущих уроков

Fucntion xor(a, b)
return (a or b) and not (a and b)
end

Не стесняйтесь использовать одни функции внутри других. Ведь в этом состоит один из секретов хорошего программирования: вы создаёте простые функции, из которых составляете более сложные, и в конце концов приходите к программе, эффективно решающей вашу задачу, к тому же легко читемую и изменяемую. Давайте с помощью составленной нами только что функции sign сделаем свой аналог функции math.abs (которую назовём просто abs):

200?"200px":""+(this.scrollHeight+5)+"px");">
-- myabs.lua

Function sign(x)
if x > 0 then
return 1
elseif x < 0 then
return -1
else
return 0
end
end

Function abs(x)
return x*sign(x)
end

Print(abs(8.88), abs(-8.88), abs(0))
-- Выведет 8.88 8.88 0

Не знаю, как вы, а моё сердце наполняется радостью при виде таких изящных решений.

Чтобы вы знали, функция может даже вызывать саму себя! Эта техника называется рекурсивным вызовом , и она нередко используется во многих алгоритмах. Как правило, используя рекурсию там, где это возможно, можно получить очень маленький и простой код, а простота и компактность есть сверхцель любого программиста (если условия работы позволяют ему задумываться о таких вещах время от времени).

Конечно, если бездумно использовать рекурсию, то функция так и будет без конца вызывать саму себя до тех пор, пока на компьютере не закончится оперативная память. Зрелище, не спорю, забавное, но такое поведение едва ли можно назвать желаемым. Если какую-то задачу планируют решать с помощью рекурсии, то в первую очередь обычно определяются граничные условия, а затем тело рекурсивного вычисления, которое с каждым новым вызовом приближается к граничным условиям. Классическое приложение рекурсивного вызова - функция, вычисляющая факториал. Как известно, факториал числа n (где n - натуральное число) определяется как:

n ! = 1 * 2 * ... * n

200?"200px":""+(this.scrollHeight+5)+"px");">
-- factorial.lua --

Рекурсивное вычисление
function fac1(n)
if n <= 1 then
return 1
else
return n*fac1(n-1)
end
end

Итеративное вычисление
-- (с помощью цикла)
function fac2(n)
fac = 1
for i = 2, n do
fac = fac * i
end
return fac
end

Хочу обратить ваше внимание: в итеративном варианте вычисления факториала мы создали переменную с названием fac , чтобы накапливать произведение чисел от одного до n. Всё бы хорошо, но эта переменная создаётся в глобальном окружении - для нас это означает, что любая определённая вне функции fac2 переменная с названием fac будет обречена на гибель после вызова функции fac2. Таким образом, если мы допишем:

200?"200px":""+(this.scrollHeight+5)+"px");">
fac = 1961 -- год рождения вашей любимой тётушки Фак
print(fac2(6)) -- хотим увидеть 720 (6!)
print(fac) -- хотим увидеть 1961, но видим 720

То результат будет удручающим. С другой стороны, можно возразить: пример уж очень натянутый (ну у кого в мире может оказаться тётушка по имени fac!), и если что можно просто избежать создания лишней переменной с именем fac, и проблема не возникнет. Так-то оно так, но если вы пишете достаточно большую программу, "запрещённых" имён переменных со временем будет становиться всё больше и больше, и в конце концов нормальных имён попросту не останется.

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

200?"200px":""+(this.scrollHeight+5)+"px");">
-- factorial2.lua --

Function fac2(n)
local fac = 1 -- приписали local
for i = 2, n do
fac = fac * i
end
return fac
end

Fac = 1961 -- не имеет ничего общего с
-- локальной переменной fac в функции fac2
print(fac2(6)) -- выведет 720
print(fac) -- выведет 1961

Заметьте: локальная переменная fac существует до окончания функции fac2, а затем уничтожается. Переменная i в цикле for также является локальной, хотя вы и не указываете это явно, и сушествует до конца цикла. То есть, локальная переменная существует до конца блока, в котором она объявлена.

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

Напоследок хочется рассказать об одной интересной возможности Lua, которой порой не хватает во многих других языках программирования: функции Lua могут возвращать несколько значений . Блестящий пример применения этой возможности - стандартная функция math.modf, которая возвращает два числа: целую и дробную части переданного параметра.

200?"200px":""+(this.scrollHeight+5)+"px");">
a, b = math.modf(1.2345)
-- Теперь a = 1, b = 0.2345

Чтобы ваша функция вернула несколько значений, достаточно указать их через запятую после инструкции return:

200?"200px":""+(this.scrollHeight+5)+"px");">
-- severalreturnvalues.lua --

Function several()
return 1, 2, 3
end

Следующий фрагмент поможет вам понять специфику вызова функции several:

200?"200px":""+(this.scrollHeight+5)+"px");">
a, b, c = several()
-- a = 1, b = 2, c = 3

D, e = several()
-- d = 1, e = 2, третье возвращаемое значение отброшено

F, g, h, i = several()
-- f = 1, g = 2, h = 3, i = nil: на i не хватило возвращаемых значений

J = several()
-- первые два значения отброшены (присвоены псевдопеременной _,
-- которая символизирует некую бездну для ненужных значений),
-- третье значение попало в переменную j: теперь j = 3

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

Подобным образом возможно инициализировать в одной строке сразу несколько переменных, вроде:

200?"200px":""+(this.scrollHeight+5)+"px");">
danger, horror, terror = "bush", false, 2^16

Но лично я не советую так делать, так как в такой записи очень просто нагородить чепухи, из-за которой программа не будет работать как надо. Гораздо нагляднее создавать переменные отдельно друг от друга:

200?"200px":""+(this.scrollHeight+5)+"px");">
danger = "bush"
horror = false
terror = 2^16

Резюме:
1. Мы научились создавать свои собственные функции.
2. Узнали о ключевом слове local , о разделении переменных на локальные и глобальные и о том, почему последние менее предпочтительны.
3. Выяснили, как вернуть из функции сразу несколько значений и как легко обменять несколько переменных значениями без привлечения каких-либо избыточных ресурсов.
sin(x) = x - (x^3)/(3!) + (x^5)/(5!) - (x^7)/(7!) + ...


Многоточие в конце означает, что дальнейшие члены суммы подчиняются такой же закономерности, и их бесконечное число. Предлагаю вам написать свою версию функции math.sin, которая бы высчитывала синус по данной формуле. Используйте для вычисления факториала функцию fac1 из приведённого выше factorial.lua .

Расширенная форма оператора for

В расширенной форме оператора for для последовательного получения значений переменной цикла используется вызов итератора. Цикл завершается, когда итератор возвращает nil.

Примечание

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

Расширенная форма оператора for имеет следующий вид:

for var1, var2, …, varN in do

… - тело цикла

var1, var2, ..., varN - список переменных, получающих значения на каждом шаге цикла. Список может состоять из одной или нескольких переменных, разделённых запятыми. Первую в списке переменную называют управляющей переменной цикла. Когда эта переменная получает возвращённое итератором значение nil, цикл завершается. Остальные переменные на ход выполнения цикла влияния не оказывают;

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

Оператор for в расширенной форме имеет те же особенности, что и числовой for:

переменные цикла var1, var2, ..., varN являются локальными для оператора цикла и по его окончании не определены;

значения переменных цикла нельзя изменять внутри цикла.

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

for key, val in pairs(t) do

Список переменных в данном примере включает два элемента - key и val. На каждом шаге цикла переменная keyполучает ключ очередного поля таблицы t, а переменная val - соответствующее ключу значение поля. В список выражений входит только один элемент - вызов функции-фабрики итераторов pairs.

Работает расширенный оператор for следующим образом:

Вызывает функцию pairs(t), от которой принимает три значения:

стандартную функцию next в качестве итератора;

таблицу, которую требуется обойти (t), в качестве состояния;

nil в качестве начального значения управляющей переменной цикла.

Вызов функции pairs выполняется только один раз.

Оператор for приступает к выполнению, собственно, итераций цикла:

вызывает функцию-итератор next с двумя параметрами: таблицей t и nil. Функция next, вызванная с этими параметрами, возвращает начальный ключ таблицы и соответствующее ему значение (при условии, что таблица не пуста);

вновь вызывает функцию next, передавая ей таблицу t и ключ, полученный на первой итерации. Функция nextвозвращает следующую пару ключ-значение. Этот процесс продолжается до тех пор, пока функция next не вернётnil.

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

local f, s, next_key = pairs(t)

local key, val = f(s, next_key)

if next_key == nil then break end

MsgBox(«key == »..key.."; val == "..val)

Выполняющий все описанные действия цикл for можно реализовать и без использования функции pairs. Известно, чтоpairs возвращает функцию next и таблицу, которую следует обойти. Поэтому вызов pairs можно заменить списком соответствующих переменных:

for key, val in next, t do

MsgBox («key == »..key.."; val == "..val)

Примечание

Помимо pairs, стандартные библиотеки Lua предоставляют ещё несколько функций-фабрик итераторов. Так, для перебора элементов массива предусмотрена функция ipairs, а для итерирования по строкам файла - функция io.lines.

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

Операторы break и return в Lua

Оператор break прерывает цикл (while, repeat или for), в теле которого встречается. В результате выполнения оператора break управление передаётся первой инструкции, следующей непосредственно за оператором цикла.

for i = 1,#a do - ищем в массиве отрицательное значение

if a[i] < 0 then - если найдено...

index = i - сохраняем индекс найденного значения...

break - и прерываем цикл

Оператор return возвращает результаты из функции (или блока).

Оператор return может просто завершать работу функции (блока), не возвращая никаких результатов.

Обратите внимание

Операторы break и return могут быть только последними операторами блока (иначе следующие за ними операторы никогда не выполнятся). Если действительно необходимо вставить return или break в середину блока, например, чтобы временно отключить выполнение части кода функции, эти операторы следует заключить в свой блок do–end.

return - НЕВЕРНО!

<другие операторы>

do return end - Правильно

<другие операторы>

Создание таблиц в Lua. Работа с полями

Создать пустую таблицу можно следующим образом:

Для доступа к полю таблицы используется запись вида:

имя_переменной[ключ]

t = {} - создаем пустую таблицу

t = «first» - новое поле таблицы, с ключом 1 и значением «first»

t = 20 - новое поле, с ключом 2 и значением 20

t[k] = «Jane» - новое поле, с ключом «name» и значением «Jane»

a = t - переменная a получает значение «first»

b = t - переменная b получает значение 20

c = t[«name»] - переменная c получает значение «name»

В случае строковых ключей вместо записи t[«name»] можно использовать запись t.name:

t.name = «name» - эквивалентно t[«name»] = «name»

a = t.name - эквивалентно a = t[«name»]

Обратите внимание

Выражение t.name не равнозначно t. Первое выражение представляет поле таблицы, ключом которого являетсястрока «name» (то есть эквивалентно t[«name»]). Второе выражение представляет поле, ключом которого является значение переменной name. Различия между этими выражениями показаны в следующем примере:

name = «somebody»

t = «Jane» - в поле «somebody» помещено значение «Jane»

a = t - переменная a получает значение поля «somebody» («Jane»)

b = t.name - поля «name» не существует, переменная b получает nil

c = t.somebody - переменная c получает значение поля «somebody» («Jane»)

Если поля таблицы с заданным ключом не существует, обращение к нему дает nil:

a = t.name - переменная a получает значение nil

Для удаления поля таблицы достаточно присвоить ему nil:

Таблицу можно заполнить значениями непосредственно при создании. Для этого в фигурных скобках следует перечислить ключи и значения элементов таблицы (в формате [ключ]=значение). Элементы отделяются друг от друга запятыми (,) или точками с запятой (;):

t = {[«red»]=«красный», [«green»]=«зеленый», [«blue»]=«синий»}

t.red = «красный»; t.green = «зеленый»; t.blue = «синий»

В случае строковых ключей квадратные скобки (и двойные кавычки) можно не указывать:

Если необходимо создать таблицу, поля которой также являются таблицами, это можно сделать следующим образом:

a = {x=20, y=1},

Приведённая запись эквивалентна следующему коду:

p.a = {x=20, y=1},

p.b = {x=40, y=2}

Работа с массивами в Lua

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

Это выражение эквивалентно следующему коду:

Обратите внимание

В Lua массивы индексируются, начиная с 1 (а не с 0, как в некоторых языках программирования).

Оператор получения длины #, применённый к массиву, возвращает его максимальный индекс (или размер):

t = {«красный», «зеленый», «синий»}

n = #t - n равно 3

В примере ниже приведён ряд типичных для Lua синтаксических конструкций (идиом), основанных на использовании оператора #:

a = t[#t] - присвоим переменной a значение последнего элемента массива t

t[#t] = nil - удалим последний элемент массива t

t[#t+1] = a - добавим значение переменной a в конец массива t

Обратите внимание

При работе с массивами следует учитывать важную особенность оператора #. Этот оператор рассматривает любой неинициализированный (имеющий значение nil) элемент массива как признак конца массива. Поэтому, например, для таблицы:

t = {=«first», =«third»} - элемент с индексом 2 отсутствует (t==nil)

оператор получения длины вернёт 1, а не 2 или 3. Таким образом, для корректной работы оператора # необходимо, чтобы массив не содержал «пустых» элементов.

Если таблица не содержит целочисленных ключей (либо элемент с индексом 1 равен nil), оператор # возвращает 0:

t = {red=«красный», green=«зеленый», blue=«синий»}

n = #t - n равно 0

t = {=«красный», =«зеленый», =«синий»}

n = #t - n равно 0 (поскольку t имеет значение nil)

Обход элементов таблицы в Lua

Для обхода всех элементов таблицы обычно используют расширенную форму оператора for совместно со стандартной функцией pairs.

t = { name = «Евгений»,

surname = «Степанов»,

for key, val in pairs(t) do

MsgBox(key..": "..val)

На каждом шаге цикла переменная key получает ключ очередного поля таблицы t, а переменная val - соответствующее ключу значение поля. Цикл выполняется по всем полям таблицы.

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

for i, val in ipairs(t) do

MsgBox("№"..i..": "..val)

На каждом шаге цикла переменная i получает числовой индекс очередного поля таблицы t, а переменная val- соответствующее индексу значение поля. Цикл продолжается до первого целого ключа, отсутствующего в таблице.

Примечание

При обходе массива необходимость в получении индекса зачастую отсутствует. В этом случае переменную i можно не вводить, заменив её символом подчеркивания (_).

t = { «Иванов», «Степанов», «Петров» }

for _, val in ipairs(t) do

Обойти массив можно и с помощью обычного числового for.

t = { «Иванов», «Степанов», «Петров» }

for i = 1, #t do

MsgBox(«Значение элемента №»..i..": "..t[i])

Определение функций в Lua

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

return x*y - тело функции

Как видно из этого примера, определение функции состоит из следующих элементов:

ключевого слова function;

имени (f в данном примере);

заключённого в круглые скобки списка аргументов функции (возможно, пустого);

тела функции, размещённого между закрывающей скобкой и ключевым словом end.

Приведённое выше определение функции является просто более удобным способом записи следующего выражения:

f = function (x,y)

return x*y - тело функции

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

Создаёт объект типа «функция» (это делает выражение function (x,y)… end).

Обратите внимание

Имя f принадлежит переменной, в которую помещена ссылка на функцию, но не самой функции. Любые функции в Lua анонимны, то есть не имеют имён. Когда говорят об имени функции, например, «функция f», на самом деле подразумевают переменную f, содержащую ссылку на эту функцию. У переменных, содержащих ссылки на функции, нет жёсткой привязки к самим функциям; работа с такими переменными осуществляется точно так же, как и с любыми другими переменными.

function f(x,y) return x*y end - создаем функцию, переменная f содержит ссылку на эту функцию

MsgBox(f(2,3))--> 6

s = f - переменная s ссылается на ту же функцию, что и f

MsgBox(s(2,3))--> 6

t.sqr = f - поле sqr таблицы t ссылается на ту же функцию, что и f

MsgBox((t.sqr(2,3))--> 6

f = MsgBox - f теперь «превратилась» в функцию MsgBox

f(«Привет, мир!») --> «Привет, мир!»

Несмотря на то, что обычно ссылка на созданную функцию присваивается переменной, такое присваивание не является обязательным. В приведённом ниже примере создаётся безымянная функция, которая сразу передаётся в качестве параметра в функцию table.sort:

table.sort (t, function (a, b) return (a > b) end)

Вызов функций в Lua

Вызов функции состоит из имени переменной, содержащей ссылку на функцию, и заключённого в круглые скобки списка аргументов (возможно, пустого).

a = f(2,3) - вызов функции f с двумя аргументами: 2 и 3

Возвращаемое функцией значение помещается в переменную a

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

require «myscript» - то же самое, что и require («myscript»)

MsgBox [[Это многострочное значение]] - то же самое, что и MsgBox([[Это многострочное значение]])

render {x=1, y=2} - то же самое, что и render({x=1, y=2})

t.f() - вызов функции, на которую ссылается поле f таблицы t

В Lua имеется возможность вызова функции в объектно-ориентированном стиле:

t:f() --эквивалентно t.f(t)

Таким образом, конструкция t:f() вызывает функцию, на которую ссылается поле f таблицы t, и эта же таблица передаётся в функцию в качестве неявного первого аргумента.

В качестве аргументов функция может принимать несколько значений. Если при вызове функции последние значения не заданы, им присваивается nil. Если значений больше, чем аргументов, «лишние» значения отбрасываются. Аргументы функции являются локальными переменными внутри функции.

function f(x, y) - определение функции

MsgBox («x=»..tostring(x).."; "..«y=»..tostring(y))

f() - вызов функции. x, y равны nil

f(1) - вызов функции. x = 1, y равен nil

f(1, 2) - вызов функции. x = 1, y = 2

f(1, 2, 3) - вызов функции. x = 1, y = 2, 3 - лишний параметр

Функции с переменным числом аргументов в Lua

Функция может принимать переменное число параметров. Для этого список аргументов в определении функции должен заканчиваться многоточием (...).

function f(x, y, ...)

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

for i = 1, arg.n do

MsgBox (tostring(arg[i]))

Возврат значений из функции Lua

Функции могут возвращать одно или несколько значений, а также не возвращать значений вообще.

function f0() - функция f0 не возвращает значений

MsgBox («Вызвана функция f0»)

MsgBox («Вызвана функция f1»)

return 1 - функция f1 возвращает одно значение

MsgBox («Вызвана функция f3»)

return 1, 2, 3 - функция f3 возвращает три значения

f0() - вызов функции f0

a = f1() - вызов функции f1. a = 1

a, b, c = f3() - вызов функции f3. a = 1, b = 2, c = 3

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

a = f3() - a = 1, значения 2 и 3 отброшены

a, b, c = f3() - a = 1, b = 2, значение 3 отброшено

a, b, c = f0() - a = nil, b = nil, c = nil

a, b, c = f1() - a = 1, b = nil, c = nil

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

f3() - значения, возвращенные функцией f3, отброшены

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

local a, b, c = f3()

Как не задавать лишних имён переменных?

Здесь общепринятым способом является использование переменной с именем «_» (символом подчёркивания). Это упрощает внешний вид выражения и указывает на реально используемые переменные.

local _, _, c = f3() - нам нужно только третье возвращаемое значение

В данном случае, переменная «_» принимает первое, а потом второе возвращаемое значение.

Такой же подход часто используется в работе с итераторами:

for _, value in pairs() do

… - нам нужно только очередное значение, ключ будет присвоен переменной _

Обратите внимание

Переменная с именем «_» является обычной переменной. При использовании этой переменной в других выражениях, она будет переопределяться, т. е. содержать то значение, которое было ей присвоено в последний раз:

local _, _, c = f3() - переменная _ будет содержать второй параметр

f4(_, true) - первым параметром функуции f4 будет не пустое значение,

а значение содеражащееся в переменной _

Возврат значений при множественном присваивании

При использовании множественного присваивания все возвращаемые функцией значения учитываются только в том случае, если вызов функции является последним (или единственным) выражением в списке выражений справа от оператора присваивания. В противном случае в присваивании участвует только одно (первое) возвращённое функцией значение.

a, b, c, d = 5, f3() - a = 5, b = 1, c = 2, d = 3

a, b, c, d = f3(), 5 - a = 1, b = 5, c = nil, d = nil

Аналогичным образом учитываются результаты вызова функции, включённого в конструктор таблицы, список аргументов другой функции и в список результатов, возвращаемых оператором return.

t = {5, f3()} - t = {5, 1, 2, 3}

t = {f3(), 5} - t = {1, 5}

func (5, f3()) - вызов функции func с аргументами 5, 1, 2, 3

func (f3(), 5) - вызов функции func с аргументами 1, 5

return 5, f3() - возврат значений 5, 1, 2, 3

return f3(), 5 - возврат значений 1, 5

Возврат функций из функций

Функции можно возвращать из функций. Например:

function outer()

function inner()

local i = outer() - значение переменной i равно функции inner

local a = i() - вызываем функцию i, значение переменной a равно 1

Базовая библиотека стандартных функций Lua

Функции базовой библиотеки Lua

Функции базовой библиотеки Lua размещаются в глобальном контексте.

В базовую библиотеку входят следующие функции:

Имя функции - Описание

assert- Генерирует ошибку с необязательным сообщением об ошибке, если значение первого аргумента false или nil.

collectgarbage -Обеспечивает интерфейс к сборщику мусора. Первый аргумент определяет выполняемое действие (остановка, запуск сборщика мусора, выполнение сборки мусора и др.).

error- Завершает выполнение последней функции, вызванной в защищённом режиме, с заданным сообщением об ошибке.

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

getmetatable- Если объект не имеет метатаблицы, возвращает nil. Иначе, если в метатаблице есть поле __metatable, возвращает значение этого поля. В противном случае возвращает метатаблицу объекта.

ipairs - Возвращает итератор, таблицу и 0. Возвращаемый итератор проходит таблицу по целочисленным индексам от значения 1 до первого индекса со значением nil. Итератор возвращает текущий индекс и соответствующее ему значение.

next- Возвращает следующий (после заданного) индекс в таблице и соответствующее ему значение. Позволяет последовательно получить все поля таблицы.

pairs - Возвращает итератор next(), таблицу и nil. Возвращаемый итератор проходит таблицу по всем значениям индекса. Итератор возвращает текущий индекс и соответствующее ему значение.

pcall - Вызывает заданную функцию с аргументами в защищённом режиме. Возвращает статус успешности выполнения.

rawequal - Сравнивает два объекта, без вызова каких-либо метаметодов. Возвращает значение типа boolean.

rawget- Возвращает реальное значение с заданным индексом из таблицы, без вызова каких-либо метаметодов.

rawset - Помещает значение в поле таблицы с заданным индексом, без вызова каких-либо метаметодов.

select - Если первый аргумент функции имеет числовое значение, возвращаются все аргументы, следующие за аргументом с этим номером. Если первый аргумент - строка ’#’, возвращается общее число полученных аргументов.

setfenv - Устанавливает таблицу в качестве контекста для заданной функции. Аргумент, задающий функцию, может быть числом, определяющим положение функции на стеке вызовов. Если он равен 0, устанавливается глобальный контекст текущего потока.

setmetatable - Устанавливает (удаляет) метатаблицу для данной таблицы. Если метатаблица содержит поле __metatable, генерирует ошибку.

tonumber - Пытается конвертировать аргумент в число. Если конвертирование не удаётся, возвращает nil.

tostring - Преобразует аргумент любого типа в строку.

type - Возвращает тип аргумента в виде строки.

unpack- Возвращает элементы из заданной таблицы.

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

Глобальные переменные

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

Имя переменной Описание

_G Таблица, содержащая глобальное окружение скрипта.

_M Таблица, содержащая текущий модуль.

Библиотека для работы со строками Lua

Все функции для работы со строками собраны в таблице string. Поэтому для вызова функций используется запись вида:

string.имя_функции(...)

Например:

Поддерживается также объектно-ориентированная форма записи. Например:

s:trim() - эквивалентно string.trim(s)

В библиотеку для работы со строками входят следующие стандартные функции:

Имя функции- Описание

len- Возвращает длину строки.

rep - Возвращает строку, содержащую указанное число копий исходной строки.

lower - Заменяет все прописные буквы в строке на строчные.

upper - Заменяет в строке все строчные буквы на прописные.

reverse - Возвращает строку, в которой символы исходной строки расположены в обратном порядке.

format- Генерирует строку по форматной строке и аргументам по правилам, принятым в языке C.

byte - Возвращает числовые коды символов строки.

char - Преобразует набор числовых кодов символов в строку.

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

match- Выполняет поиск в строке первого вхождения подстроки, соответствующей заданному шаблону. Возвращает найденную подстроку.

gmatch- Возвращает итератор, который на каждой итерации возвращает подстроку, соответствующую заданному шаблону.

sub - Возвращает подстроку исходной строки.

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

dump- Возвращает строку, содержащую двоичное представление функции Lua с заданным именем.

Библиотека для работы с таблицами Lua

Все функции для работы с таблицами собраны в таблице table. Для вызова функций используется запись вида:

table.имя_функции(...)

Например:

Имя функции- Описание

insert- Вставляет элемент в заданную позицию таблицы, сдвигая остальные элементы таблицы вправо.

remove - Удаляет заданный элемент таблицы, сдвигая остальные элементы влево. Возвращает значение удалённого элемента.

sort- Сортирует элементы таблицы в заданном порядке. Вторым аргументом может быть задана функция, которая будет использована вместо стандартного оператора «<» для сравнения элементов в процессе сортировки.

concat- Выполняет склейку указанных элементов массива через заданный разделитель. По умолчанию разделителем является пустая строка.

maxn- Возвращает наибольший положительный числовой индекс в таблице. Возвращает 0, если таблица не имеет положительных числовых индексов.

Математическая библиотека Lua

Функции математической библиотеки Lua

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

math.имя_функции(...)

Например:

В данную библиотеку включены следующие стандартные функции:

Имя функции- Описание

abs - Возвращает модуль числа.

ceil- Возвращает наименьшее целое число, большее или равное заданному (выполняет округление «вверх»).

floor- Возвращает наибольшее целое число, меньшее или равное заданному (выполняет округление «вниз»).

max- Возвращает максимальный из аргументов.

min- Возвращает минимальный из аргументов.

fmod- Возвращает остаток от деления одного числа на другое.

modf- Возвращает целую и дробную части исходного числа.

frexp- Возвращает нормализованную мантиссу и показатель аргумента.

ldexp- Строит число по мантиссе и показателю.

pow- Возводит число в степень. Вместо вызова функции возможно использование выражения вида x^y.

sqrt- Вычисляет квадратный корень числа. Вместо вызова функции возможно использование выражения вида x^0.5.

exp- Возвращает ex.

log- Вычисляет натуральный логарифм.

log10- Вычисляет логарифм по основанию 10.

cos- Вычисляет косинус угла, заданного в радианах.

sin- Вычисляет синус угла, заданного в радианах.

tan- Вычисляет тангенс угла, заданного в радианах.

cosh- Вычисляет гиперболический косинус.

sinh- Вычисляет гиперболический синус.

tanh- Вычисляет гиперболический тангенс.

acos- Вычисляет арккосинус (в радианах).

asin- Вычисляет арксинус (в радианах).

atan- Вычисляет арктангенс (в радианах).

atan2- Возвращает арктангенс x/y (в радианах), но использует знаки обоих параметров для вычисления «четверти» на плоскости. Также корректно обрабатывает случай когда y равен нулю.

deg- Переводит величину угла из радиан в градусы.

rad- Переводит величину угла из градусов в радианы.

random- Функция, вызванная без аргументов, возвращает псевдослучайное число из интервала . Эта же функция, вызванная с двумя аргументами l, u возвращает целое псевдослучайное число из интервала .

randomseed Устанавливает стартовое число генератора псевдослучайных чисел.

Переменные математической библиотеки Lua

Таблица math предоставляет следующие переменные:

Имя переменной - Описание

huge- Наибольшее представимое число.

pi- Число пи.

Библиотека ввода/вывода в Lua

Библиотека ввода-вывода предоставляет два различных «стиля» для работы с файлами.

Использование неявных дескрипторов файлов

Существуют операции для установки стандартных (используемых по умолчанию) файлов ввода и файлов вывода, и все операции ввода/вывода работают с этими файлами. Функции для работы с неявными дескрипторами собраныв таблице io.

Для вызова функций используется запись вида:

io.имя_функции(...)

Например:

Ниже приведён список стандартных функций ввода/вывода, предоставляемых таблицей io:

Имя функции - Описание

open- Открывает файл в заданном режиме.

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

output- Аналогична io.input, но работает со стандартным файлом вывода.

close- Закрывает файл. Эквивалентна file:close. Вызванная без параметра, закрывает стандартный файл вывода.

read- Аналогична io.input():read.

lines- Открывает файл с заданным именем в режиме чтения и возвращает функцию-итератор (iterator function), которая при каждом последующем вызове возвращает новую строчку из файла. При обнаружении функцией-итератором конца файла, она возвращает nil (для окончания цикла) и автоматически закрывает файл. Вызов io.lines без указания имени файла эквивалентен io.input():lines(); таким образом, он обрабатывает строки стандартного файла ввода. В этом случае файл по окончанию итераций не закрывается автоматически.

write- Эквивалентна io.output():write.

flush- Сохраняет все данные, записанные в стандартный поток вывода. Эквивалентна file:flush для стандартного потока вывода.

tmpfile- Возвращает дескриптор для временного файла. Этот файл открывается в режиме изменения и автоматически удаляется при завершении программы.

type- Проверяет, является ли объект, переданный функции в качестве параметра, корректным дескриптором файла. Возвращает строку «file», если объект является открытым дескриптором файла; строку «closed file» - если объект является закрытым дескриптором файла; nil - если объект не является дескриптором файла.

Использование явных дескрипторов файлов

При использовании явных файловых дескрипторов сначала создаётся объект-дескриптор file (для этого используется функция io.open), после чего все операции с файлом выполняются при помощи методов данного дескриптора.

Для вызова методов используется запись вида:

handle: имя_метода(...),

где handle - имя переменной, содержащей объект-дескриптор.

Ниже приведён список стандартных функций ввода/вывода, предоставляемых объектом file:

Имя функции- Описание

read- Читает данные из файла в соответствии с заданными форматами.

lines- Возвращает функцию-итератор, которая при каждом вызове возвращает новую строку из файла. В отличие от io.lines, эта функция не закрывает файл по окончании цикла (т. е. достижении конца файла). При вызове функции без параметров, возвращает текущий файл ввода по умолчанию. В случае ошибок, данная функция возбуждает ошибку вместо того, чтобы возвратить код ошибки.

write- Записывает в файл значения всех своих аргументов.

flush- Сохраняет все данные, записанные в файл.

setvbuf- Устанавливает режим буферизации для выходного файла.

seek- Устанавливает текущую позицию в файле.

close- Закрывает файл.

Библиотека для доступа к средствам операционной системы в Lua

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

os.имя_функции(...)

Например:

В данную библиотеку включены следующие стандартные функции:

Имя функции- Описание

clock- Возвращает примерное количество времени (в секундах), которое программа выполнялась на CPU.

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

difftime- Возвращает число секунд, прошедшее от времени t1 до времени t2.

execute- Передаёт заданную команду на исполнение оболочке операционной системы.

getenv- Возвращает значение заданной переменной окружения или nil, если переменная не определена.

remove- Удаляет файл или папку с заданным именем. Папки должны быть пусты.

rename- Переименовывает файл или папку.

time- Возвращает текущее время при вызове без аргументов, или время и дату, указанные в передаваемой таблице. Эта таблица должна иметь поля year, month, и day, и может иметь поля hour, min, sec, и isdst.

tmpname- Возвращает строку с именем файла, который может быть использован в качестве временного файла. Файл должен быть явно открыт до использования и явно удалён, если больше не будет

Всем привет.

Сегодня мы поверхностно пройдёмся по языку Lua, его некоторым возможностям, а так же запуске наших сценариев в RakBot.
Lua - скриптовый язык программирования, предназначен для быстрой обработки данных. С помощью данного языка многие разработчики создают искусственный интелект в играх, пишут алгоритмы генерации уровней, а так же он используется для разработки ресурсов/игровых модов в Multi Theft Auto: San Andreas (аналог SA:MP). На самом деле, это простейший язык и с помощью него мы будем учиться писать собственную логику для ботов, которую будет использовать RakBot.

Пройдёмся по основам программирования, с которыми нам предстоит работать.

Обратите внимание : данная статья будет урезана в плане языка Lua, так как в RakBot используется лишь небольшая её часть. Многие возможности Lua попросту отсустствуют в RakBot, поэтому я буду ориентироваться на версию из RakBot.

Есть традиция у всех авторов книг и документаций различных языков, это первая программа, которая печатает "Hello World".
Чтож, давайте попробуем написать её, но уже в RakBot. Переходим на оффициальный сайт RakBot и ищем раздел "Доступные функции", раздел "События".

Нам необходимо событие onScriptStart() , которые вызывается автоматически при загрузке скрипта самим RakBot"ом.

В этой функции нам необходимо описать логику, которая будет писать в чат-лог RakBot"a "Hello World". Для этого, на той же странице в документации, посмотрим на раздел "Функции".

Первая фукнция printLog(text) - это то, что нам и нужно. С помощью этой функции мы отправим сообщение в чат RakBot"а. Для этого мы напишем:

Мы написали логику в каком-то текстовом документе, но как сказать RakBot, чтобы он выполнил наш сценарий? Для этого необходимо сохранить файл с расширением .lua и положить его в папку scripts , в папке с RakBot.
Я сохранил текстовый документ с именем "example.lua ". Давайте попробуем запустить RakBot и посмотреть, что у нас получилось.

Как мы видим, при запуске RakBot, он находит скрипт "example.lua ", после чего выполняет его. Из этого мы можем сделать вывод, что инициализация сценария происходит при запуске самого RakBot или при перезагрузке всех сценариев командой !reloadscripts .

Поздравляю, Вы только что написали свой собственный сценарий для RakBot!

Мы уже научились писать Hello World в консоли RakBot"а, но мы хотим писать сложных ботов, которые будут делать всю работу за нас, учитывая те или иные условия. На этом мы остановимся.
Практически всё, что происходит в программировании, можно описать следующим образом: возьми данные, что-то с ними сделай, отдай результат.
В данном случае данными выступает сам RakBot. Он сам запускает наши сценарии, а так же сам передаёт нам данные, которые мы можем обработать так, как хотим и в конце получить результат.

Давайте напишем простейший сценарий с условием. Условием будет являться ник бота. Если ник бота "СМaster", значит мы выведем в чат RakBot"а "CM FOREVER", если же ник бота совершенно другой - выведем в чат "Nonamer".
Для этого нам поможет условный оператор if else , он же оператор ветвления. Он принимает на себя условие, которое должно вернуть либо true , либо false . Если условие равно true , тогда код внутри будет выполнен, если false - не будет выполнен.
На этом строится большая часть логики любого приложения. Дословно if переводится как "ЕСЛИ", then - "ЗНАЧИТ", else - "ИНАЧЕ" Если это сильно сложно - не переживайте, Вы поймёте всё дальше.

В Lua есть следующие операторы сравнения:
> Больше
< Меньше
>= Больше или равно
<= Меньше или равно
~= Не равно
== Равно

Если мы напишем "CMaster " == "CM " - у нас будет значение False , то есть, ложь
Если мы напишем "CMaster " == "CMaster " - у нас будет значение True , то есть, истина.

5 > 10 -- ложь 5 < 10 -- истина 10 ~= 15 -- истина 10 >= 5 -- истина

Попробуем использовать логику ветвления в нашем предыдущем сценарии.

Тот код, который мы писали ранее:

Function onScriptStart() printLog("Hello world!"); end

Преобразуем следующим образом:

Function onScriptStart() botName = getNickName() if(botName == "CMaster") then printLog("CM FOREVER"); else printLog("Nonamer"); end end

Давайте разберём этот код начиная сверху. Я советую сразу начинать учить читать код. Поэтому попробуем прочитать, что у нас получилось

Function onScriptStart() - создаём фукнцию с именем onScriptStart botName = getNickName() - в переменную botName записываем имя бота if(botName == "CMaster") then - если имя бота равно "CMaster", значит printLog("CM FOREVER"); - пишем в чат "CM Forever". else- ИНАЧЕ, или же если имя бота НЕ РАВНО "CMaster" printLog("Nonamer"); - пишем в чат "Nonamer" end- конец условий end- конец функции

Давайте попробуем проверить код, который мы написали. Я сохранил измененный код так же под именем "example.lua " и запустил RakBot с ником "Mason_Bennett ".

После загрузки нашего сценария, RakBot написал в чат Nonamer. Попробуем зайти с ником "CMaster ".

Как мы видим, наше условие успешно работает и мы видим в чате то, что и хотели.

Пройдёмся немного по переменным. У Вас есть лист бумаги и Вы хотите его сохранить. Сохранить каким образом - куда-то положить, чтобы не потерять его. Например, мы можем положить наш лист бумаги в шкафчик и достать тогда, когда нам будет необходимо. Если у нас будет новый листок и нам не нужен будет старый - мы выкинем старый листок и положим новый.
Это и есть логика переменной. Мы можем создавать переменную с именами, которыми хотим и записывать в них значения, что мы и сделали в предыдущем примере с переменной botName.

В Lua мы можем записывать в переменную всё, что мы хотим. Например, я хочу создать переменную с именем PaperList и записать в неё текст "Lua - урок №2 ". Для этого я напишу:

PaperList = "Lua - урок №1"

Что мы здесь сделали? Написали имя и использовали оператор присваивания "=" и теперь я могу использовать эту переменную в любом месте своего сценария.
Думаю, что если Вы вспомните математику на уровне максимум 5 класса - тут будет всё понятно.

У Lua есть несколько типов переменных, это nil, boolean, number, string . Не бойтесь, это всё очень просто.

На самом деле их несколько больше, но я уже говорил, что в RakBot большая часть функционала отсутствует.

nil - отсуствие значения.
boolean - логические значения, принимает два варианта значений - либо true, либо false.
number - вещественное число с двойной точностью. В Lua нет целочисленного типа, поэтому он выступает в качестве и вещественного и целочисленного типа.
string - строка, здесь, я думаю, всё понятно.
Чтож, давайте попробуем создать несколько переменных и "поиграться" с ними.

number = 0; - создаём переменную с именем number и присваиваем значение 0
number = number + 5; - присваивание значения переменной number + 5 (то есть, 0 + 5), теперь у нас хранится здесь число 5.
number ++; - ++ - инкремент. Другими словами - вы берёте переменную и увеличиваете её на одну единицу. То есть (5 + 1) - теперь 6 лежит у нас в переменной number.
number --; - -- декремент. Другими словами - уменьшаем на одну единицу. (6 - 1) - теперь значение равно 5.

string = "Hello" - создаём переменную string со значением "Hello"
string = string .. "," - конкатенация строк, оно же сложение строк. Что мы здесь сделали? Указани имя переменной, указали оператор конкатенации ".. ", после чего указали ещё одну строку, которую необходимо добавить к первой. Теперь у нас в переменной "string " лежит значение "Hello,".
string = string .. getNickName () - теперь, к "Hello," мы добавили ник бота, пускай будет "Michel". Теперь у нас в переменной string лежит значение "Hello,Michel".

boolean = true ; - создаём переменную boolean со значением true (ИСТИНА).
boolean = getNickName () == "Dimosha" - сравниваем имя бота со строкой Dimosha. Так как имя бота у нас Michel, из предыдущего примера, сюда запишется значение false (ЛОЖЬ).

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

Давайте пройдёмся по самому простому пути: фукнция без параметров и без возвращаемого значения, которая будет складывать 5 + 10 и выводить результат в консоль RakBot"а.

Я создам функцию с именем Add :

Function Add() -- Создаём фукнцию Add printLog(5 + 10) -- используем метод RakBot для вывода в консоль end-- Конец функции

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

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

Function Add(a, b) printLog(5 + 10) end

Теперь в методе у нас доступны два значения, которые содержатся в двух новых переменных a и b, но на консоль у меня всё равно выводится 15. Исправим это:

Function Add(a, b) printLog(a + b) end

Идеально. Теперь, при вызове этого метода, мы будем получать результат сложения в консоли. Попробуем протестировать. Изменим наш код в example.lua на следующий:

Function Add(a, b) printLog(a + b) end function onScriptStart() Add(5, 10); Add(123, 4324); Add(555, 111); end

И попробуем запустить RakBot. Посмотрим, что из этого получится:

Это решило нашу первую проблему. Попробуем решить вторую, чтобы наша функция возвращала результат.

Перепишем фукнцию Add :

Function Add(a, b) return a + b end

return - ключевое слово для возвращения значения из функции. Перепишем теперь метод onScriptStart :

Function onScriptStart() printLog("Первое значение: "..Add(5, 10)); printLog("Второе значение: "..Add(123, 4324)); printLog("Третье значение: "..Add(555, 111)); end

Посмотрим, что получилось.

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

Теперь, мы научились создавать собственные фукнции с параметрами, без параметров и возвращать из них значения. Я считаю, что этих основ Вам хватит сполна, чтобы написать собственного бота в рамках RakBot"а. В следующих уроках мы будем создавать простейшего бота, которого будем постепенно усложнять, добавляя всё новые и новые возможности и фукнции.