Использованные библиотеки:

  • 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)


  1. Rive
    04.08.2021 11:39
    +4

    Если такой сахар замедляет выполнение кода, то и смысла в нём особого нет, потому что Lua обычно выбирают не за сахар, а за максимально высокую скорость выполнения из всех встраиваемых скриптовых языков.

    Хотя с лямбдами кодить всё равно приятнее, чем без них.


  1. Nnnnoooo
    04.08.2021 14:31

    интересный костыль, но замедление будет значительное — почти весь код через eval прогоняется, о всяких luaJit можно забыть


  1. berez
    04.08.2021 21:07

    Я лямбдами в луа пользовался безо всяких библиотек еще лет десять назад. Зачем делать какой-то новый синтаксис, если все уже есть в языке?


  1. Alex-111
    05.08.2021 07:30
    +1

    Идея интересная, короткой записи лямбд действительно не хватает в lua. Но мне кажется тут лучше поправить код интерпретатора добавив простой синтаксический сахар в виде замены конструкции x, y, z, ... -> expr на function(x, y, z, ...) return expr end. К тому же -> и => не заняты в языке. Это и по производительности дешево выйдет, а то, как уже выше заметили, в вашей реализации такое удобство выглядит дороговато.


    1. allcreater
      06.08.2021 12:42

      Недостаток авторского решения в отсутствии синтаксической подсветки лямбд (стринга и стринга), но в случае изменения интерпретатора придётся и вовсе патчить плагин IDE, чтобы он тоже понимал такие выкрутасы… Хотя по производительности и правда гораздо дешевле