
Использованные библиотеки:
inspect для строковой репрезентации
strong для обработки строк
Предположим, мы хотим написать функцию, которая выводит наглядное представление значения переменной. Наши лямбды должны делать следующие две строки эквивалентными:
local show = f[[x -> print(inspect(x))]] -- lambdas local show = function(x) print(inspect(x)) end -- anonymous functions
В целом выглядит не так уж впечатляюще, но лямбды дадут значительный выигрыш в читаемости при написании библиотеки с method chaining:
-- lambdas local var = {3, 12, 14, 42, 44, 51, "a"} / filter[[type(it) == "number"]] / map[[it ^ 2 - 1]] / filter[[it % 24 == 0]] / map[[it .. " probably can be prime"]] -- anonymous functions local var = {3, 12, 14, 42, 44, 51, "a"} / filter(function(it) return type(it) == "number" end) / map(function(it) return it ^ 2 - 1 end) / filter(function(it) return it % 24 == 0 end) / map(function(it) return it .. " probably can be prime")
Поскольку вызов filter[[text]] это просто альтернативная форма записи для filter("text"), с помощью простой конкатенации строк внутри filter и map мы сможем научить их работать с котлин-лямбдами.
Приступим к разработке. В целом функция создания лямбды f должна производить примитивную подстановку:
-- f[[<args> -> <result>]] ===> function(<args>) return <result> end local function f(text) local a, b = text:find(" %-> ") local args = text:sub(0, a - 1) local result = text:sub(b + 1) local function_text = "function(%s) return %s end" % {args, result} local loading_function = load("return " .. function_text) if loading_function == nil then -- вывод ошибки если не получилось загрузить error("Incorrect lambda " .. inspect(function_text)) end return loading_function() end
Всю работу производит функция load, которая динамически интерпретирует строку с кодом. Она возвращает функцию без аргументов, подгружающую содержание строки, либо nil если что-то пошло не так.
local show = f[[x -> print(inspect(x))]] show{message = "Hello, world!"} --[[ ВЫВОД: { message = "Hello, world! } ]]
Функция show сработает, если вы импортировали модуль inspect в глобальную переменную, и выдаст ошибку, если в локальную. Дело в том, что load использует окружение функции f, а модуль inspect существует в окружении на уровень выше. Для решения этой проблемы можно написать функцию, которая добавляет в текущее окружение локальные переменные на уровень выше.
local function push_environment(env, delta) local i = 1 while true do local name, value = debug.getlocal(3 + (delta or 0), i) if not name then return end env[name] = value i = i + 1 end end
debug.getlocal(n, m) позволяет получить доступ к локальной переменной с порядковым номером m на n уровней выше по стеку. n = 1 соответствует push_environment, n = 2 -- f, n = 3 -- функции, внутри которой f вызвана, что нам и нужно. Аргумент delta добавлен на всякий случай для расширяемости.
Добавляем push_environment(_ENV) в начало функции f и тадам! -- всё работает.
Если вы хотите реализовать method chaining как в начале статьи, то это может выглядеть примерно следующим образом:
fnl.filter = fnl.pipe() .. function(self, predicate) if type(predicate) == "string" then predicate = f("ix, it -> " .. predicate) end local result = {} for i, v in ipairs(self) do if predicate(i, v) then table.insert(result, v) end end return result end
В данном случае fnl.pipe() инкапсулирует перегрузку операторов, необходимую для method chaining.
Стоит заметить, что использование функций таблицы debug и интерпретация lua-кода в рантайме замедляют скорость выполнения кода, поэтому я рекомендую пользоваться этим решением с осторожностью.
