Добавить в корзинуПозвонить
Найти в Дзене
LUA/QUIK

Lua "Hello World"

Простейшая программа на Lua, напечатаем: "Hello World": В качестве более сложного примера используем объявление переменной. Глобальным переменным не нужны объявления; вы их просто используете. Обратиться к неинициализированной переменной не является ошибкой; вы всего лишь получите значение nil в качестве результата: Если вы присвоите nil глобальной переменной, то Lua поведет себя так, как если бы эта переменная никогда не использовалась: Кроме глобальных, Lua поддерживает и локальные переменные. Мы создаем локальные переменные при помощи оператора local: В отличие от глобальных переменных, область видимости локальной переменной ограничена блоком, где она была объявлена. Блок — это тело управляющей структуры, тело функции или кусок кода (файл или строка, где переменная была объявлена): Следующий пример с оператором if: Таким образом, вы можете записывать локальные объявления везде, где вы можете записать оператор. Область видимости объявленных переменных начинается сразу пос
Оглавление

Простейшая программа на Lua, напечатаем: "Hello World":

  • print("Hello World")

В качестве более сложного примера используем объявление переменной.

Глобальным переменным не нужны объявления; вы их просто используете.

Обратиться к неинициализированной переменной не является ошибкой; вы всего лишь получите значение nil в качестве результата:

  • print(b) --> nil
  • b = 10
  • print(b) --> 10

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

  • b = nil print(b) --> nil

Кроме глобальных, Lua поддерживает и локальные переменные. Мы создаем локальные переменные при помощи оператора local:

  • j = 10 -- глобальная переменная local
  • i = 1 -- локальная переменная
В отличие от глобальных переменных, область видимости локальной переменной ограничена блоком, где она была объявлена.

Блокэто тело управляющей структуры, тело функции или кусок кода (файл или строка, где переменная была объявлена):

  • x = 10
  • local i = 1 -- локальная для куска
  • while i <= x do -- условие выполняется пока х не достигнет значения равного i
  • local x = i*2 -- локальная для тела while
  • print(x) --> 2, 4, 6, 8, ...
  • i = i + 1
  • end

Следующий пример с оператором if:

  • x = 10 -- глобальная
  • local i = 1 -- локальная для куска
  • if i > 20 then -- если условие выполняется, то:
  • local x -- локальная для тела "then"
  • x = 20 -- значение локального х
  • print(x + 2) -- (напечатает 22, если условие выполнится)
  • else print(x) --> 10 (глобальная переменная)
  • end -- завершает оператор if
  • print(x) --> 10 (глобальная переменная)

Таким образом, вы можете записывать локальные объявления везде, где вы можете записать оператор.

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

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

  • local a, b = 1, 10
  • if a < b then
  • print(a) --> 1
  • local a -- неявное '= nil'
  • print(a) --> nil
  • end -- завершает блок, начатый с 'then'
  • print(a, b) --> 1 10

Цикл for будет выполняться пока верно условие:

  • a=6
  • for i=1, a do print (i) end --> 1 2 3 ... -- от i=1 до а=6 с шагом по умолчанию 1

Функции

Функции могут выполнять определенное задание или вычислять и возвращать значения. В первом случае мы используем вызов функции как оператор; во втором случае мы используем его как выражение:

  • function f (a, b) -- f название функции; a,b список аргументов
  • Iocal c=a+b
  • return c -- оператор return возвращает результаты из функции, если они есть, или просто завершает её.
  • end
  • print(f(3,5)) -->8

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

  • print "Hello World" <--> print("Hello World")

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

  • function f (a, b)
  • Iocal c=a+b
  • local d=a*b
  • return c, d
  • end
  • print(f(3,5)) -->8, 15

Вот пример функции с оператором if внутри:

  • function f (a, b)
  • local c, d = 0, 0 -- допускается объявление нескольких переменных
  • if a>b then c=a-b; d=a/b
  • elseif a<b then c=a+b; d=a*b
  • else c=a; d=b
  • end
  • return c, d
  • end
  • print (f(6,2)) --> 4 3

Функция также может выступать в качестве аргумента:

  • print (f(f(6,2),2)) --> 2 2

При этом будет браться только первое значение функции (4).

Теперь добавим в функцию цикл for и таблицу:

  • function f (a, b)
  • local c, d = 0, 0
  • local t={} -- объявляем пустую таблицу
  • for i=1, a do
  • if a>b then c=a-b; d=a/b;
  • t[i]=c*i+b+i -- i- индекс ячейки в таблице
  • elseif a<b then c=a+b; d=a*b;
  • t[i]=c/i+b-i
  • else c=a; d=b; t[i]=(c+b)^i
  • end
  • end
  • return t
  • end

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

  • local funt=f(4,2)
  • for i=1, # funt do
  • print (funt[i]) --> 5 8 11 14
  • end

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

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

  • function add (…)
  • local s = 0 for i, v in ipairs{…} do
  • s = s + v
  • end
  • return s
  • end
  • print(add(3, 4, 10, 25, 12)) --> 54

Три точки (…) в списке параметров указывают на то, что эта функция является вариадической. При вызове этой функции Lua внутренним образом собирает все ее аргументы; мы называем эти собранные аргументы дополнительными аргументами функции. Функция может получать доступ к своим дополнительным аргументам опять же при помощи трех точек, теперь уже в качестве выражения. В нашем примере выражение {…} производит массив со всеми собранными аргументами. Затем функция обходит массив для сложения его элементов. Мы называем выражение с … выражением с переменным числом аргументов (vararg expression). Оно ведет себя как функция с возвратом нескольких значений, возвращая все дополнительные аргументы текущей функции. Например, команда print(…) напечатает все дополнительные аргументы текущей функции. Аналогично, следующая команда создаст две локальные переменные со значениями первых двух необязательных аргументов (или nil, если таких аргументов нет).

  • local a, b = …

На самом деле мы можем имитировать стандартный механизм передачи параметров в Lua, переводя

  • function foo (a, b, c)

в

  • function foo (…)
  • local a, b, c = …

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

  • function id (…) return … end

Lua предоставляет отдельные функции для форматирования текста (string.format) и его записи (io.write). Несложно объединить обе функции в одну вариадическую:

  • function fwrite (fmt, …)
  • return io.write(string.format(fmt, …))
  • end

Обратите внимание на присутствие фиксированного параметра fmt перед точками. Вариадические функции могут иметь любое количество фиксированных параметров перед своей вариадической частью. Lua присваивает этим параметрам первые аргументы; остальные (если есть) идут как дополнительные параметры. Вызовы и значения соответствующих параметров:

  • fwrite() fmt = nil, – без дополнительных аргументов
  • fwrite("a") fmt = "a", – без дополнительных аргументов
  • fwrite("%d%d", 4, 5) fmt = "%d%d", – доп. аргументы = 4 и 5

(Обратите внимание, что вызов fwrite() приведет к ошибке, поскольку string.format требует строку в качестве своего первого аргумента.) Для обхода своих дополнительных аргументов функция может использовать выражение {…}, чтобы собрать их всех в таблицу, как мы это сделали в нашем определении add. Однако, в тех редких случаях, когда дополнительные аргументы могут быть допустимыми nil, таблица, созданная при помощи {…}, может не быть правильной последовательностью. Например, для такой таблицы не существует способа узнать, были ли конечные nil в исходных аргументах. Для этих случаев Lua предлагает функцию table.pack. Эта функция получает произвольное число аргументов и возвращает новую таблицу со всеми своими аргументами, как и {…}, но в этой таблице будет дополнительное поле 'n', содержащее полное число ее аргументов. Следующая функция использует table.pack, чтобы проверить, что среди ее аргументов нет nil:

  • function nonils (…)
  • local arg = table.pack(…)
  • for i = 1, arg.n do
  • if arg[i] == nil then return false end
  • end
  • return true
  • end
  • print(nonils(2,3,nil)) --> false
  • print(nonils(2,3)) --> true
  • print(nonils()) --> true
  • print(nonils(nil)) --> false

Однако, не забывайте, что {…} быстрее и понятнее, чем table.pack(…), когда среди дополнительных аргументов не может быть nil.

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

По сути, стандартный способ написать функцию в Lua, такой как

  • function foo (x) return 2*x end

это просто более красивый способ написать следующий код:

  • foo = function (x) return 2*x end

Таким образом, определение функции — это по сути оператор (присваивание, если более точно), который создает значение типа "function" и присваивает его переменной.

Мы можем рассматривать выражение function (х) тело end как конструктор функции, точно так же, как {} является конструктором таблицы. Результат подобных конструкторов функций называется анонимной функцией. В основном функции присваиваются глобальным переменным, задавая им что-то вроде имени, но так же бывают случаи, когда функции остаются анонимными. Табличная библиотека предоставляет функцию table.sort, которая получает таблицу и сортирует ее элементы. Подобная функция должна позволять бесконечные вариации порядка сортировки: по возрастанию или по убыванию, числовой или алфавитный, таблицы с сортировкой по ключу и т. д. Вместо попытки предоставить все виды опций, sort предоставляет единственный необязательный параметр, который является порядковой функцией: функция, которая принимает два элемента и определяет, должен ли первый элемент идти перед вторым в отсортированном списке. Например, допустим, что у нас есть такая таблица записей:

  • network = {
  • {name = "grauna", IP = "210.26.30.34"},
  • {name = "arraial", IP = "210.26.30.23"},
  • {name = "lua", IP = "210.26.23.12"},
  • {name = "derain", IP = "210.26.23.20"},
  • }

Если нам нужно отсортировать таблицу по полю name в обратном алфавитном порядке, то достаточно написать так:

  • table.sort(network, function (a,b) return (a.name > b.name) end)

Функция, которая получает другую функцию как аргумент, такая как sort, является функцией высшего порядка.

Функции высшего порядка являются эффективным механизмом программирования, а использование анонимных функций в качестве их аргументов служит колоссальным источником гибкости. Функции высших порядков не являются чем-то особенным; они — прямое следствие способности Lua работать с функциями как со значениями первого класса. Чтобы проиллюстрировать применение функций высших порядков, рассмотрим упрощенное определение распространенной функции высшего порядка — производной. В качестве примера возьмём, производную функции f в точке х — (f (x + d) − f (x))/d, где d стремится к бесконечно малой величине. В соответствии с этим определением мы можем вычислить приближенное значение производной следующим образом:

  • function derivative (f, delta)
  • delta = delta or 1e-4
  • return function (x)
  • return (f(x + delta) - f(x))/delta
  • end
  • end

Если задать функцию f, вызов derivative(f) вернет ее производную (как приближенное значение), которая является еще одной функцией:

  • c = derivative(math.sin)
  • > print(math.cos(5.2), c(5.2))

--> 0.46851667130038 0.46856084325086

  • print(math.cos(10), c(10))

--> -0.83907152907645 -0.83904432662041

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

Замыкания

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

Начнем с простого примера:

  • function newCounter ()
  • local i = 0
  • return function () -- анонимная функция
  • i = i + 1
  • return i
  • end
  • end
  • c1 = newCounter()
  • print(c1()) --> 1
  • print(c1()) --> 2

В этом коде анонимная функция ссылается на нелокальную переменную i для хранения своего счетчика. Однако, к тому времени, как мы вызовем анонимную функцию, переменная i уже выйдет из своей области видимости, поскольку функция, которая создала эту переменную (newCounter), уже будет возвращена. Тем не менее, Lua правильно обрабатывает эту ситуацию, используя концепцию замыкания (closure). Проще говоря, замыкание — это функция вместе со всем, что ей нужно для правильного доступа к нелокальным переменным. Если мы снова вызовем newCounter, то она создаст новую локальную переменную i, поэтому мы получим новое замыкание, действующее поверх новой переменной:

  • c2 = newCounter()
  • print(c2()) --> 1
  • print(c1()) --> 3
  • print(c2()) --> 2

Таким образом, c1 и с2 — это разные замыкания поверх одной и той же функции, и каждое замыкание воздействует на независимый экземпляр локальной переменной i. С технической точки зрения, значением в Lua является замыкание, а не функция. Сама по себе функция — это лишь прототип для замыканий. Во многих случаях замыкания оказываются ценным инструментом.

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

  • oldSin = math.sin
  • math.sin = function (x)
  • return oldSin(x*math.pi/180)
  • end
  • print(math.sin(90))
  • print(oldSin(90))

Теперь мы сохраняем старую версию в локальной переменной; единственный способ обратиться к ней — через новую функцию.