API для реализации функциональных интерфейсов для OneScript
Пакет лежит в основном пуле хаба, а opm знает этот хаб по умолчанию: короткой формы достаточно.
opm install lambdasСкачанный файл ставится командой opm install -f <файл> — сеть для этого уже не нужна.
Описание
lambdas
Лямбдас это библиотека для реализации функциональных интерфейсов в ваших библиотеках
How to
Синтаксис лямбда выражения:
Поддерживаются следующие варианты синтаксиса лямбда выражений:
// 1
"(ПараметрПервый, ПараметрВторой) -> { Возврат ПараметрПервый + ПараметрВторой }"
// 2
"(ПараметрПервый, ПараметрВторой) -> Возврат ПараметрПервый + ПараметрВторой"
// 3
"ПараметрПервый, ПараметрВторой -> { Возврат ПараметрПервый + ПараметрВторой }"
// 4
"ПараметрПервый, ПараметрВторой -> Возврат ПараметрПервый + ПараметрВторой"
Простой пример
Допустим, у вас есть библиотека, в рамках которой вы хотите позволить потребителю удалять элементы из массива по некоторому условию.
Вы добавляете метод:
// МояКласснаяБиблиотека.os
Процедура УдалитьПоУсловию(Условие) Экспорт
Действие = Лямбда.Выражение(Условие)
.ВДействие();
Для каждого Элемент из Коллекция Цикл
Если Действие.Выполнить() Тогда
Коллекция.Удалить(Элемент);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
В данном примере вы получаете Действие на метод, который был создан по переданному выражению, и вызов вашего метода теперь может выглядеть вот так:
// ПотребительМоейКласснойБиблиотеки.os
Процедура Удалить()
// Удалим из коллекции все элементы которые больше или равны двум
МояКласснаяБиблиотека.УдалитьПоУсловию("Элемент -> Возврат Элемент >= 2");
КонецПроцедуры
Интерфейсы
Для лямбда выражения можно указать функциональный интерфейс который она будет реализовывать, эта возможность преследует две цели
- Проверить что переданное выражение соответствует вашим ожиданиям, Функция это или Процедура, и количество параметров
- Предоставить возможность не писать возврат в однострочных лямбда выражениях вашим потребителям, если это функция
В составе библиотеки поставляет ряд функциональных интерфейсов (в модуле ФункциональныеИнтерфейсы):
- Вызываемый - Функция
Вызватьбез параметров - УниФункция - Функция
Применитьс одним параметром - БиФункция - Функция
Применитьс двумя параметром - ТриФункция - Функция
Применитьс тремя параметром - Запускаемый - Процедура
Запуститьбез параметров - УниПроцедура - Процедура
Принятьс одним параметром - БиПроцедура - Процедура
Принятьс двумя параметрами - ТриПроцедура - Процедура
Принятьс тремя параметрами
Так же можно установить произвольный ИнтерфейсОбъекта для создания метода по нему
Если интерфейс не был передан явно, то будет предпринята попытка автоматически подобрать один из интерфейсов из списка выше, в зависимости от количества параметров лямбда выражения и наличия возврата значения в его тексте
Например:
// МояКласснаяБиблиотека.os
Процедура Сортировать(СравнениеЗначений) Экспорт
Интерфейс = Новый ИнтерфейсОбъекта();
Интерфейс.ФункцияИнтерфейса("Сравнить", 2);
Компаратор = Лямбда.Выражение(СравнениеЗначений)
.Интерфейс(Интерфейс)
.ВОбъект();
Для каждого Элемент Из Коллекция Цикл
РезультатСравнения = Компаратор.Сравнить(Элемент, СледующийЭлемент);
Если РезультатСравнения > 0 Тогда
// ...
ИначеЕсли РезультатСравнения < 0 Тогда
// ...
Иначе
// ...
КонецЕсли;
// Алгоритм сортировки
КонецЦикла;
КонецПроцедуры
Тут мы использовали метод ВОбъект для получения сценария, который реализует наш переданный интерфейс, и обратились по имени метода, который мы отразили в интерфейсе.
Вызов метода:
// ПотребительМоейКласснойБиблиотеки.os
МояКласснаяБиблиотека.Сортировать("Первый, Второй ->
| Если Первый > Второй Тогда
| Возврат 1;
| ИначеЕсли Второй > Первый Тогда
| Возврат -1;
| Иначе
| Возврат 0;
| КонецЕсли;"
);
Пример необязательного возврата:
// МояКласснаяБиблиотека.os
Процедура Заменить(Массив, Выражение) Экспорт
Действие = Лямбда.Выражение(Выражение)
.Интерфейс(ФункциональныеИнтерфейсы.УниФункция())
.ВДействие();
Счетчик = 0;
Для Счетчик = 0 По Массив.ВГраница() Цикл
Массив.Установить(Счетчик, Действие.Выполнить(Массив[Счетчик]));
КонецЦикла;
КонецПроцедуры
// ПотребительМоейКласснойБиблиотеки.os
Массив = Новый Массив();
Массив.Добавить(1);
Массив.Добавить(2);
Массив.Добавить(3);
Заменить(Массив, "Элемент -> Элемент + 1");
Сообщить(Массив[0]);
Сообщить(Массив[1]);
Сообщить(Массив[2]);
Результат:
// 2
// 3
// 4
Контекст
Лямбда-выражению можно установить Контекст, это позволит использовать поля контекста прямо в выражении, контекстом может выступать только структура или фиксированная структура
Например:
// МояКласснаяБиблиотека.os
Процедура СделайДело(ЧтоСделать) Экспорт
ДелоПервое = Новый Структура("Состояние", "НеСделано");
ДелоВторое = Новый Структура("Состояние", "НеСделано");
Контекст = Новый Структура(
"ДелоПервое, ДелоВторое",
ДелоПервое,
ДелоВторое
);
Делатель = Лямбда.Выражение(ЧтоСделать)
.Контекст(Контекст)
.ВДействие();
Делатель.Выполнить(1);
Сообщить("ДелоПервое = " + ДелоПервое.Состояние);
Сообщить("ДелоВторое = " + ДелоВторое.Состояние);
КонецПроцедуры
Вызов метода:
// ПотребительМоейКласснойБиблиотеки.os
МояКласснаяБиблиотека.СделайДело("(НомерДела) ->
| Если НомерДела = 1 Тогда
| ДелоПервое.Состояние = ""Сделано""
| ИначеЕсли НомерДела = 2 Тогда
| ДелоВторое.Состояние = ""Сделано""
| КонецЕсли;"
);
Результат:
ДелоПервое = Сделано
ДелоВторое = НеСделано
Как мы видим, поля контекста доступны в лямбда выражении по именам ключей структуры контекста
Значения контекста копируются в момент вызова ВДействие или ВОбъект: лямбда хранит их снимок, а не саму структуру. Поэтому:
- Изменения структуры после создания лямбды, в том числе из других потоков, на неё не влияют. Чтобы лямбда увидела новые значения, создайте её заново, это дёшево благодаря кешированию
- Присваивание полю контекста внутри лямбды действует только в пределах одного вызова: структура контекста не меняется, а следующий вызов снова увидит исходное значение. Чтобы вернуть результат через контекст, передайте в нём объект и меняйте его, как
ДелоПервоев примере выше - Параметры лямбды и переменные, объявленные в её теле через
Перем, скрывают одноимённые поля контекста
Контекст = Новый Структура("Порог", 5);
Больше = Лямбда.Выражение("Элемент -> Возврат Элемент > Порог")
.Контекст(Контекст)
.ВДействие();
Контекст.Порог = 10;
Больше.Выполнить(7); // Истина: лямбда видит Порог = 5
Захват объекта
В лямбда выражение можно захватить какой-то объект, чтобы в тексте лямбда выражения можно было использовать публичные и приватные поля объекта, а также публичные методы
Например:
// МояКласснаяБиблиотека.os
Функция Единичка(Алгоритм, Объект) Экспорт
Действие = Лямбда.Выражение(Алгоритм)
.ЗахватитьОбъект(Объект)
.ВДействие();
Возврат Действие.Выполнить(1);
КонецФункции
Вызов метода:
// ПотребительМоейКласснойБиблиотеки.os
Перем Двойка;
Перем Тройка Экспорт;
Функция Четверка() Экспорт
Возврат 4;
КонецФункции
Результат = МояКласснаяБиблиотека.Единичка(
"(Единица) -> Возврат Единица + Двойка + Тройка + Четверка()",
ЭтотОбъект
);
// Результат = 10
Как мы видим, в выражении возможно обратиться к полям и методам переданного объекта
Аннотации
Есть возможность указать Аннотации как для параметров метода так и для самого метода, создаваемого из лямбда-выражения, например:
"&АннотацияМетода (Первый, Второй) -> Возврат Первый + Второй"
Будет преобразовано в:
&АннотацияМетода
Функция Принять(Первый, Второй)
Возврат Первый + Второй;
КонецФункции
Так же и для параметров:
"(&АннотацияПараметра(1) Первый, &АннотацияПараметра(2) Второй) -> Возврат Первый + Второй"
Будет преобразовано в:
Функция Принять(&АннотацияПараметра(1) Первый, &АннотацияПараметра(2) Второй)
Возврат Первый + Второй;
КонецФункции
Важно
При использовании аннотаций требуется использовать полную форму указания параметров т.е. со скобками(Параметр1, Параметр2).
В такой форме аннотации, оставшиеся за скобками параметров, будут принадлежать методу, а внутри скобок - параметрам, перед которым они идут.
Так же блок параметров должен быть отделён пробелом от самой аннотации, в ином случае они будут прочитаны как параметры аннотации:
&Аннотация (Парам1, Парам2) // <- Метод с двумя параметрами и аннотацией
&Аннотация(Парам1, Парам2) // <- Метод без параметров с аннотацией с двумя параметрами
Кеширование
Создание объекта лямбды требует компиляции сценария, поэтому созданные объекты кешируются (последние 50). ВДействие и ВОбъект возвращают уже созданный объект, если совпадают выражение, интерфейс, захваченный объект и значения контекста. Значения сравниваются вместе с типом: примитивные по значению (строки с учётом регистра), объекты по ссылке. Поэтому лямбду можно создавать заново на каждый вызов, в том числе с новой структурой контекста.
Значения контекста объект лямбды хранит в неизменяемом снимке и при каждом вызове читает в локальные переменные метода, поэтому один объект из кеша можно одновременно использовать из нескольких потоков. Исключение составляют лямбды с захваченным объектом: они работают с полями этого экземпляра.
Программный интерфейс
Лямбда
Выражение
// Создаёт лямбда-выражение из переданного выражения
//
// Параметры:
// Выражение - Строка - Выражение, из которого будет создано лямбда-выражение
//
// Возвращаемое значение:
// ЛямбдаВыражение - Созданное лямбда-выражение
//
Функция Выражение(Выражение)
ЛямбдаВыражение
ПриСозданииОбъекта
Процедура ПриСозданииОбъекта(Выражение)
Интерфейс
// Устанавливает функциональный интерфейс для лямбда выражения.
// В случае если метод интерфейса это функция:
// 1. Добавляет "Возврат" в лямбда выражение если оно однострочное и не содержит возврат
// 2. Проверяет что многострочное выражение содержит возврат значения
// В случае В случае если метод интерфейса это процедура:
// 1. Проверяет что выражение не содержит возврат значения
//
// Параметры:
// Интерфейс - ИнтерфейсОбъекта - Устанавливаемый функциональный интерфейс
//
// Возвращаемое значение:
// ЛямбдаВыражение - Инстанс текущего выражения
//
Функция Интерфейс(Интерфейс)
Контекст
// Устанавливает контекст для лямбда выражения.
// При исполнении лямбда выражения все значения контекста будут доступны для использования в лямбда выражении.
// Значения копируются в момент создания объекта лямбды (ВОбъект, ВДействие): последующие изменения
// структуры на лямбду не влияют, а присваивания переменным контекста внутри лямбды действуют
// только в пределах одного вызова
//
// Параметры:
// Контекст - Структура, ФиксированнаяСтруктура - Устанавливаемый контекст
//
// Возвращаемое значение:
// ЛямбдаВыражение - Инстанс текущего выражения
//
Функция Контекст(Контекст)
ЗахватитьОбъект
// Захватывает переданный объект для использования его в качестве контекста лямбда выражения.
// При исполнении лямбда выражения в нём будут доступны публичные и приватные поля переданного объекта
// а так же публичные методы.
//
// Параметры:
// Объект - Сценарий - Захватываемый объект
//
// Возвращаемое значение:
// ЛямбдаВыражение - Инстанс текущего выражения
//
Функция ЗахватитьОбъект(Объект)
ВДействие
// Возвращает действие (делегат) на метод созданный по лямбда выражению
//
// Возвращаемое значение:
// Действие - Действие на метод лямбда выражения
//
Функция ВДействие()
ВОбъект
// Возвращает сценарий созданный по лямбда выражению, который содержит метод согласно интерфейса.
// Сценарий может быть получен из кеша: для одинаковых выражения, интерфейса, захваченного объекта
// и значений контекста возвращается один и тот же сценарий
//
// Возвращаемое значение:
// Сценарий - Созданный сценарий
//
Функция ВОбъект()
ФункциональныеИнтерфейсы
Вызываемый
// Возвращает интерфейс объекта содержащий функцию без параметров
//
// Возвращаемое значение:
// ИнтерфейсОбъекта - Искомый интерфейс
//
Функция Вызываемый() Экспорт
УниФункция
// Возвращает интерфейс объекта содержащий функцию с одним параметром
//
// Возвращаемое значение:
// ИнтерфейсОбъекта - Искомый интерфейс
//
Функция УниФункция()
БиФункция
// Возвращает интерфейс объекта содержащий функцию с двумя параметрами
//
// Возвращаемое значение:
// ИнтерфейсОбъекта - Искомый интерфейс
//
Функция БиФункция()
ТриФункция
// Возвращает интерфейс объекта содержащий функцию с тремя параметрами
//
// Возвращаемое значение:
// ИнтерфейсОбъекта - Искомый интерфейс
//
Функция ТриФункция()
Запускаемый
// Возвращает интерфейс объекта содержащий процедуру без параметров
//
// Возвращаемое значение:
// ИнтерфейсОбъекта - Искомый интерфейс
//
Функция Запускаемый() Экспорт
УниПроцедура
// Возвращает интерфейс объекта содержащий процедуру с одним параметром
//
// Возвращаемое значение:
// ИнтерфейсОбъекта - Искомый интерфейс
//
Функция УниПроцедура()
БиПроцедура
// Возвращает интерфейс объекта содержащий процедуру с двумя параметрами
//
// Возвращаемое значение:
// ИнтерфейсОбъекта - Искомый интерфейс
//
Функция БиПроцедура()
ТриПроцедура
// Возвращает интерфейс объекта содержащий процедуру с тремя параметрами
//
// Возвращаемое значение:
// ИнтерфейсОбъекта - Искомый интерфейс
//
Функция ТриПроцедура()