Использованные библиотеки:
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-кода в рантайме замедляют скорость выполнения кода, поэтому я рекомендую пользоваться этим решением с осторожностью.
Комментарии (5)
Nnnnoooo
04.08.2021 14:31интересный костыль, но замедление будет значительное — почти весь код через eval прогоняется, о всяких luaJit можно забыть
berez
04.08.2021 21:07Я лямбдами в луа пользовался безо всяких библиотек еще лет десять назад. Зачем делать какой-то новый синтаксис, если все уже есть в языке?
Alex-111
05.08.2021 07:30+1Идея интересная, короткой записи лямбд действительно не хватает в lua. Но мне кажется тут лучше поправить код интерпретатора добавив простой синтаксический сахар в виде замены конструкции
x, y, z, ... -> expr
наfunction(x, y, z, ...) return expr end
. К тому же->
и=>
не заняты в языке. Это и по производительности дешево выйдет, а то, как уже выше заметили, в вашей реализации такое удобство выглядит дороговато.allcreater
06.08.2021 12:42Недостаток авторского решения в отсутствии синтаксической подсветки лямбд (стринга и стринга), но в случае изменения интерпретатора придётся и вовсе патчить плагин IDE, чтобы он тоже понимал такие выкрутасы… Хотя по производительности и правда гораздо дешевле
Rive
Если такой сахар замедляет выполнение кода, то и смысла в нём особого нет, потому что Lua обычно выбирают не за сахар, а за максимально высокую скорость выполнения из всех встраиваемых скриптовых языков.
Хотя с лямбдами кодить всё равно приятнее, чем без них.