В продолжение статьи про функции первого класса. Рекомендую ознакомиться прежде, чем читать эту.
Начав говорить о функциях первого класса, невозможно обойти стороной тему лямбда-функций.
Немного теории
Лямбда-функция или лямбда-выражение - это анонимная (т.е. не имеющая имени) функция первого класса, тело которой описывается непосредственно в коде создающей стороны, а ссылка на нее сразу присваивается переменной либо передается в параметре другой функции. Как правило, это несложная "одноразовая" функция, которую нет смысла описывать отдельно, т.к. ее повторное использование не предполагается. Либо функция настолько проста, что ее проще определить по месту, пускай и повторно в разных местах, чем добавлять в виде глобальной именованной функции.
Возможность реализации в 1С
В языках, поддерживающих лямбда-функции, они более или менее органично вписаны в общий синтаксис языка. Во встроенном языке 1С такой поддержки нет, поэтому лямбды могут быть реализованы только в виде динамически компилируемого "кода в кавычках" (хотя в 1С:Элемент реализовали поддержку лямбда-выражений, как и вообще функций первого класса).
Были и раньше попытки реализовать лямбда-подобные решения в 1С, например в этой статье (спойлер: там не настоящие лямбды, т.к. они не обладают свойствами функций первого класса).
Я не сторонник злоупотребления динамически компилируемым кодом и считаю, что его следует использовать как можно реже.
Во-1х, его невозможно отлаживать и синтаксический контроль для него работает только на этапе выполнения. Во-2х, динамическая компиляция требует дополнительного времени, которое прямо зависит от размера компилируемого текста, что может серьезно снижать производительность в высокочастотных участках программы. В-3х, его неудобно писать, для него не работает подсветка синтаксиса и в контексте окружающего "нормального" кода 1С он выглядит, как белая ворона.
Но, с другой стороны, примерно те же недостатки имеет код на языке запросов, что не мешает нам его использовать и считать это обычным делом. Все просто привыкли.
Следует признать, что порой написать лямбда-однострочник удобнее, чем оформлять отдельную функцию (если, конечно, вы вообще используете функции первого класса). Разумеется, не следует писать в таком стиле целые простыни кода. 1-3 строки максимум.
В общем, все нижеследующее является скорее экспериментами и я никоим образом не рекомендую это к промышленному применению. Всё, что можно сделать при помощи лямбд, можно сделать и без лямбд, используя функтор.
Если сказанное до сих пор не отвратило вас от этой темы, читаем дальше.
Лямбда
Моя лямбда-функция реализована по образу и подобию Функтора. Работает только на сервере.
Пример тривиального лямбда-однострочника:
УмножительНаТри = Лямбда("а,м", "=а*м", , 3);
// использование:
Результат = УмножительНаТри.Вызвать(2); // Результат = 6
Пример посложнее, с накоплением данных в контексте замыкания:
Сумматор = Лямбда("а, @сум", "сум = сум + а; _=сум", , 0);
// использование:
Для каждого Элемент из Массив Цикл
Сумматор.Вызвать(Элемент);
КонецЦикла;
СуммаЭлементов = Сумматор.Результат();
Здесь уже появляется непривычный синтаксис, всякие "@" и "_=". Сейчас разберем, что это такое.
Общий синтаксис создания лямбда-функции:
Функ = Лямбда(<ОписаниеПараметров>, <ТелоФункции>, <Параметр>, <Параметр>, ...);
- <ОписаниеПараметров> - Строка - Список имен параметров функции, разделенных запятыми.
Имя параметра может иметь префикс "@" - это означает, что параметр является возвращаемым. Параметры без такого префикса передаются "по значению". - <ТелоФункции> - Строка - Любая разрешенная последовательность выражений на встроенном языке 1С.
Если функция должна вернуть результат вычисления первого выражения, то текст должен начинаться с символа "=" (как в примере "УмножительНаТри").
Если первое выражение не возвращает результат, тогда его может вернуть любое из следующих выражений, присвоив его переменной с именем "_" (подчеркивание). Отсюда и выражение вида "_=сум" в примере "Сумматор". Жаль, что в динамически компилируемом коде нельзя использовать привычный оператор "Возврат", но я не вижу смысла тратиться на его имитацию. - <Параметр>, <Параметр>, ... - Произвольные параметры, максимум 8 штук. Они соответствуют параметрам функции и служат для создания контекста замыкания. Т.е. это значения, которые передает создающая сторона. Поскольку лямбда-функция не вызывается сразу в момент создания, значения параметров будут инкапсулированы вместе с функцией и использованы только в момент вызова.
Как и для функтора, в начале списка параметров рекомендуется располагать параметры, передаваемые вызывающей стороной, а в конце списка - параметры контекста замыкания, передаваемые создающей стороной.
Общий синтаксис вызова лямбда-функции:
Результат = Функ.Вызвать(<Параметр>, <Параметр>, ...);
Как видим, вызов ничем не отличается от вызова функтора, т.к. лямбда и есть функтор, только создаваемый по-другому.
Любая сторона, владеющая ссылкой на функцию, может получить последний возвращенный ею результат таким способом:
Результат = Функ.Результат();
Также может быть получено текущее значение любого из параметров контекста замыкания, по индексу параметра:
Значение = Функ.Параметр(<Индекс>);
Пример. Универсальный обход дерева
А вот так будет выглядеть пример с универсальным обходом дерева из предыдущей статьи. Напомню: нам нужно рекурсивно обходить все узлы дерева и с каждым узлом выполнять некоторое действие. Дерево одно, а действий много и все разные.
Пускай наше дерево содержит, среди прочих, колонки с числами и колонку Пометка (Булево). В примере реализуем несколько операций. Одна будет вычислять максимум значения численной колонки по всем узлам дерева. Вторая будет собирать массив всех узлов, где Пометка = Истина. Третья будет устанавливать значение Пометка во всех узлах.
Универсальная функция рекурсивного обхода остается неизменной:
Функция ОбходДерева(Узел, Действие)
Для каждого Строка Из Узел.Строки Цикл
Действие.Вызвать(Строка);
ОбходДерева(Строка, Действие);
КонецЦикла;
Возврат Действие;
КонецФункции
А вот дальше мы сокращаем количество объявляемых функций вдвое, т.к. теперь функции действий можно описать в виде лямбда-выражений.
Функция МаксимумПоКолонке(Дерево, Колонка) Экспорт
Возврат ОбходДерева(Дерево,
Лямбда("Строка,Колонка,@Максимум",
"Максимум = Макс(Строка[Колонка], Максимум); _= Максимум",, Колонка, 0))
.Результат(); // результат последнего вызова функции действия
КонецФункции
Функция ПомеченныеУзлы(Дерево) Экспорт
Возврат ОбходДерева(Дерево,
Лямбда("Строка,Узлы",
"Если Строка.Пометка Тогда Узлы.Добавить(Строка); КонецЕсли; _= Узлы",, Новый Массив))
.Результат(); // результат последнего вызова функции действия
КонецФункции
Процедура УстановитьПометку(Дерево, Пометка) Экспорт
ОбходДерева(Дерево, Лямбда("Строка,Пометка", "Строка.Пометка = Пометка",, Пометка));
КонецПроцедуры
Что под капотом?
Вступайте в нашу телеграмм-группу Инфостарт