Функции первого класса, высшего порядка и анонимные в Lua
Опубликовано:
В данной статье мы разбираем, что значит функции первого класса, функции высшего порядка и анонимные функции, а также почему в Lua этим понятиям уделяется достаточно большое внимание.
Почему функции в Lua первого класса
В Lua определении функции как в большинстве других языков, когда после ключевого слова function идет имя-идентификатор функции, является всего лишь синтаксическим "сахаром". На самом деле происходит присваивание функции (как своего рода значения в виде блока кода) переменной, и подобное в Lua широко используется явным образом:
local function greet1(name) print('Hello '..name) end local greet2 = function(name) local s = 'Hello '..name return s end print(type(greet1)) -- function print(type(greet2)) -- function greet1('Tom') -- Hello Tom print(greet2('Bob')) -- Hello Bob
В Lua функции принадлежат одному из типов данных языка — function. При этом с ними можно делать все тоже самое, что c другими типами данных, то есть присваивать переменным, передавать в функции как аргументы, возвращать из функций, хранить в таблице. Именно из этого вытекает, что такие функции являются функциями первого класса (first class function), то есть у них нет никаких ограничений на использование.
Концепция функций первого класса как свойство самого языка программирования поддерживается и в других языках, например Python. Однако там она не так актуальна, так как на уровне синтаксиса есть классы, декораторы и др. В Lua же функции лежат в основе эмуляции ООП. Без них невозможно построить сложные структуры данных. Чтобы создать "объект" с "методами", надо непосредственно записать функции как значения в ячейки таблицы. ООП в Lua — это просто таблицы, где лежат функции.
Понятие функций первого класса фактически не отделимо от двух других — функций высшего порядка и анонимных, потому что иначе в первых не было бы особого смысла. Ведь основное их применение — быть переданными в другие функции и быть возвращенными из них.
Функции высшего порядка
Язык может позволять определять функции высшего порядка (higher-order functions), но это не значит, что каждая таковой является. Функция высшего порядка — это какие-то конкретные функции. Их особенностью является способность принимать другие функции в виде аргументов и/или возвращать функции. Другими словами, не все функции имеют признак высшего порядка.
Простой пример функции, которая принимает другую:
local function greetUser(callback) local n = io.read() print(callback()..n) end local greet = function() return 'Hello ' end greetUser(greet)
Rob Hello Rob
В листинге ниже показан уже пример функции, которая возвращает другую. Так переменным red и white присваиваются эти возвращенные функции, в которые благодаря наличию замыканий в Lua "вшита" переменная first со своим значением у каждой:
local function pairColor(first) return function (second) print(first..'-'..second) end end local red = pairColor('Red') red('Blue') red('Green') local white = pairColor('White') white('Yellow') white('Pink')
Red-Blue Red-Green White-Yellow White-Pink
Более сложный пример:
-- Функция высшего порядка: принимает таблицу и функцию-фильтр local function filter_table(tbl, test_func) local result = {} for _, value in ipairs(tbl) do -- Вызываем функцию, которую нам передали в качестве аргумента if test_func(value) then table.insert(result, value) end end return result end local numbers = {1, 12, 5, 8, 15} -- Передаем анонимную функцию как аргумент local big_numbers = filter_table(numbers, function(x) return x > 10 end) -- Выведет: 12, 15 for _, v in ipairs(big_numbers) do print(v) end
Все функции в Lua изначально анонимны
В коде выше в качестве аргумента мы передаем анонимную функцию. Таковой она является по той причине, что у нее нет имени. Однако в языке Lua абсолютно все функции изначально являются анонимными. Ведь если функция — это такое же значение базового типа данных (как число, строка или таблица), она сама по себе не имеет имени, у нее есть лишь значение-код. Именованной функция становится только тогда, когда происходит присваивание этого значения какой-либо переменной или сохранение в поле таблицы.
В отличие от Python (где анонимные функции создаются только с помощью lambda), в Lua внутри анонимной функции можно писать сколько угодно сложную логику. Для Lua анонимность функций — это не бонус, добавленный поверх основного языка. Здесь анонимность лежит в основе базовой архитектуры.
Анонимные функции как есть используются не только в качестве аргументов, но и когда надо обработать данные "на лету":
print((function(num) if num % 2 == 0 then return 'Четное' else return 'Нечетное' end end)(io.read('n')))
5 Нечетное
Возвращаемая из функции высшего порядка функция в большинстве случаев является анонимной. Наглядно посмотреть как используются анонимные функции в Lua можно на примере фабрик итераторов. При всем этом наиболее широкое применение они находят в качестве коллбэков и событийно-ориентированном программировании, в функциях высшего порядка для сортировки и фильтрации, в функциональном ООП и модулях.