Функции первого класса, высшего порядка и анонимные в 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 можно на примере фабрик итераторов. При всем этом наиболее широкое применение они находят в качестве коллбэков и событийно-ориентированном программировании, в функциях высшего порядка для сортировки и фильтрации, в функциональном ООП и модулях.