Coding — Введение в Lua для программистов — часть 2.3
Немного раньше я показал работу с функциями. Теперь я расскажу о типах userdata и thread, а так же затрону тему области видимости переменных.
Тип данных userdata стоит несколько особняком. По сути это аватар произвольной C-структуры в мире Lua. Или, если угодно, своеобразная франшиза, бренд, под которым в Lua видны кастомные типы данных. Строго говоря нет такого одного отдельного типа данных — userdata, есть множество различных разнородных кастомных типов данных, которые создаются либо в C-библиотеках, либо в хост-приложении, которое пользует Lua (не забываем, что Lua очень часто используется не как самостоятельный интерпретатор, а как библиотека, прилинкованная к «большому» приложению, и вот это приложение может создать произвольное окружение для выполняемых в его рамках Lua-скриптов; оно и будет хостом для Lua-программ). В Lua все такие кастомные типы (обычно это C-шные struct-ы, но это необязательно) видны как один тип — userdata, хотя и природа данных под этим типом может быть совершенно разная.
Примером типа userdata может, например, послужить объект screen в Awesome:
В данном случае хост-приложение Awesome передаёт в Lua-окружение специальную таблицу-массив screen, элементы которых содержать объекты, представляющие физический экран. Хотя внешне эти элементы видны как объекты типа userdata, фактически это объекты типа screen, предоставляющие интерфейс к физическому экрану: его разрешение, рабочую область за вычетом зон, занятых виджетами самого WM и порядковый номер экрана. Точно так же Awesome определяет несколько других типов данных в Lua для манипуляции с запущенными приложениями, изображениями и т.д. Подробнее можно про этот зоопарк почитать в официальной документации.
Таким образом userdata является собирательным образом всех кастомных типов, которые для Lua предоставляет C-часть, поэтому описывать его практически невозможно: нужно брать конкретную реализацию, библиотеку, смотреть конкретные типы данных и читать про их интерфейс, методы и свойства, которые они определяют. Создать такой кастомный тип средствами самого Lua невозможно, да, как правило, и не нужно.
Lua поддерживает исполнение кооперативных потоков, для этого есть специальная библиотека coroutine. Если честно я сомневаюсь, стоит ли рассказывать сейчас про все её фичи, потому что сопрограммы и мультритрединг (в любом виде, в том числе и кооперативный) — это тема отдельной большой статьи, которую я ещё напишу. Поэтому здесь я просто упомяну, что такая библиотека есть, а тем, кому не в терпёж, (и позволяет теоретическая и практическая подготовка) могут прочитать про неё в документации к Lua прямо сейчас. Если кратко, то объекты типа thread создаются функциями из библиотеки coroutine, представляют один поток выполнения и предоставляют интерфейс для манипуляции с этим потоком.
А теперь так же кратенько о переменных. Переменные по области видимости в Lua делятся на локальные и глобальные. Все переменные, которые создаются в программе, являются глобальными, если особо не заявлено обратное:
Как видите обе переменные a и b глобальны, хотя вне тела функции создана только переменная b.
Локальные переменные объявляются с помощью ключевого слова local, закрывая по необходимости глобальные переменные с теми же именами:
Оператор local действует в пределах текущего блока операторов, при этом действие его начинается в момент определения переменной с этим ключевым словом (как видно в примере функция seta() могла модифицировать глобальную переменную до тех пор, пока она не была перекрыта локальной, объявленной оператором local).
Кроме того можно совместить присваивание переменной значения с её объявлением через local.
Локальные переменные могут быть созданы не только в пределах функции, но и в пределах любого блока операторов:
Если хочется локальной переменной только в некотором куске кода, и не хочется объявлять циклов и функций, то можно воспользоваться универсальными операторными скобками do ... end:
Локальными могут быть и функции, ведь объявление функции это всего навсего присваивание объекта типа функция некоторой переменной:
Локальные переменные имеют лексическую область видимости:
(Динамическое связывание переменных можно эмулировать через заворачивание функции в другую функцию, которая сохраняет в своей локальной переменной необходимые глобальные переменные, назначает им новые значения, вызывает первую функцию и восстанавливает глобальные переменные из локальной копии. Это ни разу ни thread-safe, так что использовать нужно с умом. Подробнее этот и другие хаки описаны здесь.)
Естественно все параметры функции автоматически являются локальными для этой функции. Кроме того, локальные переменные могут попадать в замыкания (пример смотрите в конце предыдущей статьи).
Итог: мы наскоро познакомились с двумя последними типами данных — userdata и thread, узнали про локальные переменные (которые объявляются оператором local и имеют лексическую область видимости) и универсальный операторный блок do ... end. Кроме того внимательных читателей ожидал бонус в виде оператора break для досрочного выхода из цикла.
В следующей статье я хочу устроить интерлюдию, в которой собираюсь привести пример рабочей программы на основе уже изложенного материала. Какой именно пример показать я пока ещё не придумал, поэтому у читателей есть шанс предложить свои варианты. Или может у кого уже есть какие-то наброски по тексту статей? Идеи прошу в комменты!
Франшиза
Тип данных userdata стоит несколько особняком. По сути это аватар произвольной C-структуры в мире Lua. Или, если угодно, своеобразная франшиза, бренд, под которым в Lua видны кастомные типы данных. Строго говоря нет такого одного отдельного типа данных — userdata, есть множество различных разнородных кастомных типов данных, которые создаются либо в C-библиотеках, либо в хост-приложении, которое пользует Lua (не забываем, что Lua очень часто используется не как самостоятельный интерпретатор, а как библиотека, прилинкованная к «большому» приложению, и вот это приложение может создать произвольное окружение для выполняемых в его рамках Lua-скриптов; оно и будет хостом для Lua-программ). В Lua все такие кастомные типы (обычно это C-шные struct-ы, но это необязательно) видны как один тип — userdata, хотя и природа данных под этим типом может быть совершенно разная.
Примером типа userdata может, например, послужить объект screen в Awesome:
1 2 3 4 5 6 7 |
|
В данном случае хост-приложение Awesome передаёт в Lua-окружение специальную таблицу-массив screen, элементы которых содержать объекты, представляющие физический экран. Хотя внешне эти элементы видны как объекты типа userdata, фактически это объекты типа screen, предоставляющие интерфейс к физическому экрану: его разрешение, рабочую область за вычетом зон, занятых виджетами самого WM и порядковый номер экрана. Точно так же Awesome определяет несколько других типов данных в Lua для манипуляции с запущенными приложениями, изображениями и т.д. Подробнее можно про этот зоопарк почитать в официальной документации.
Таким образом userdata является собирательным образом всех кастомных типов, которые для Lua предоставляет C-часть, поэтому описывать его практически невозможно: нужно брать конкретную реализацию, библиотеку, смотреть конкретные типы данных и читать про их интерфейс, методы и свойства, которые они определяют. Создать такой кастомный тип средствами самого Lua невозможно, да, как правило, и не нужно.
Клубок нитей
Lua поддерживает исполнение кооперативных потоков, для этого есть специальная библиотека coroutine. Если честно я сомневаюсь, стоит ли рассказывать сейчас про все её фичи, потому что сопрограммы и мультритрединг (в любом виде, в том числе и кооперативный) — это тема отдельной большой статьи, которую я ещё напишу. Поэтому здесь я просто упомяну, что такая библиотека есть, а тем, кому не в терпёж, (и позволяет теоретическая и практическая подготовка) могут прочитать про неё в документации к Lua прямо сейчас. Если кратко, то объекты типа thread создаются функциями из библиотеки coroutine, представляют один поток выполнения и предоставляют интерфейс для манипуляции с этим потоком.
Переменные под прикрытием
А теперь так же кратенько о переменных. Переменные по области видимости в Lua делятся на локальные и глобальные. Все переменные, которые создаются в программе, являются глобальными, если особо не заявлено обратное:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Как видите обе переменные a и b глобальны, хотя вне тела функции создана только переменная b.
Локальные переменные объявляются с помощью ключевого слова local, закрывая по необходимости глобальные переменные с теми же именами:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
Оператор local действует в пределах текущего блока операторов, при этом действие его начинается в момент определения переменной с этим ключевым словом (как видно в примере функция seta() могла модифицировать глобальную переменную до тех пор, пока она не была перекрыта локальной, объявленной оператором local).
Кроме того можно совместить присваивание переменной значения с её объявлением через local.
Локальные переменные могут быть созданы не только в пределах функции, но и в пределах любого блока операторов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
Если хочется локальной переменной только в некотором куске кода, и не хочется объявлять циклов и функций, то можно воспользоваться универсальными операторными скобками do ... end:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Локальными могут быть и функции, ведь объявление функции это всего навсего присваивание объекта типа функция некоторой переменной:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
Локальные переменные имеют лексическую область видимости:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
(Динамическое связывание переменных можно эмулировать через заворачивание функции в другую функцию, которая сохраняет в своей локальной переменной необходимые глобальные переменные, назначает им новые значения, вызывает первую функцию и восстанавливает глобальные переменные из локальной копии. Это ни разу ни thread-safe, так что использовать нужно с умом. Подробнее этот и другие хаки описаны здесь.)
Естественно все параметры функции автоматически являются локальными для этой функции. Кроме того, локальные переменные могут попадать в замыкания (пример смотрите в конце предыдущей статьи).
Итог: мы наскоро познакомились с двумя последними типами данных — userdata и thread, узнали про локальные переменные (которые объявляются оператором local и имеют лексическую область видимости) и универсальный операторный блок do ... end. Кроме того внимательных читателей ожидал бонус в виде оператора break для досрочного выхода из цикла.
В следующей статье я хочу устроить интерлюдию, в которой собираюсь привести пример рабочей программы на основе уже изложенного материала. Какой именно пример показать я пока ещё не придумал, поэтому у читателей есть шанс предложить свои варианты. Или может у кого уже есть какие-то наброски по тексту статей? Идеи прошу в комменты!