Улучшаем 1С: Функции первого класса

15.03.26

Разработка - Универсальные функции

Добавляем элементы функционального программирования в 1С.

Не претендую на превращение 1С в функциональный язык... но функцию первого класса удалось соорудить.

И нет, здесь не будет ничего про лямбды. И никакого "кода в кавычках".

(Но если вам интересны лямбды - пишите в комментариях, у меня и лямбды имеются)

 

Немного теории

(Те, кто знаком с функциями первого класса, могут пропустить этот раздел)

Всем известно, что мы можем хранить данные в переменных и передавать их в параметрах функций. Также известно, что концепция объекта позволяет нам хранить и передавать не только данные, но и поведение, определенное в виде методов объекта.

В большинстве современных языков есть еще один способ хранить и передавать поведение - это так называемые "функции первого класса" (first-class functions).

Первое, что отличает функцию 1-го класса от обычной функции: ее можно передать в параметре другой функции или присвоить переменной. Именно саму функцию, а не ее результат. По сути, это означает передачу ссылки на функцию, а далее можно вызвать функцию по этой ссылке, передав ей требуемые параметры, и получить результат. В функциональных языках все функции по умолчанию обладают этим свойством. Но и во многих императивных языках есть такая возможность.

В терминах ООП можно сказать, что функция 1-го класса - это эквивалент объекта с одним единственным методом. Но при этом не требуется специально описывать отдельный класс - достаточно описать только функцию.

Второе важное понятие - замыкание (closure). Это способность функции 1-го класса использовать переменные окружающего контекста, существовавшие в момент ее создания, даже когда она вызывается в совершенно другом контексте. В языках, поддерживающих замыкание на уровне синтаксиса, это действительно выглядит так, что функция В, созданная внутри функции А, ссылается на локальные переменные функции А. Хотя реально вызов функции В может происходить где-то в функции С, куда функцию В передали в качестве параметра.

Если отбросить синтаксический сахар, то суть замыкания в том, что сторона, создавшая функцию, может параметризировать ее алгоритм своими локальными значениями. Но не путем передачи их в параметрах функции (поскольку она лишь объявляет, но не вызывает функцию), а путем инкапсуляции этих значений вместе с функцией в единый объект.

Таким образом, второе важное свойство функции 1-го класса: ее алгоритм может получать одну часть параметров от стороны, создающей функцию, а другую часть параметров - от стороны, вызывающей функцию.

 

Функтор

Мое решение предназначено для использования на сервере, а синтаксически максимально приближено к "функциональному" виду. Я назвал его "Функтор", чтобы не путать с традиционными функциями. К тому же, мой функтор более всего похож на класс-функтор в С++, хотя и имеет от него ряд отличий. Впрочем, в разных языках программирования термин Функтор обозначает совершенно разные концепции, вот и у меня будет свой функтор с блэкджеком и гуриями.

Создается функтор так:

Функ = Функтор(<Объект>, <ИмяФункции>, <Параметр>, <Параметр>, ...);

<Объект> - Любой объект, содержащий функции. Это может быть общий модуль, модуль менеджера, экземпляр объекта конфигурации или объекта платформы.

<ИмяФункции> - Строка - Имя функции в объекте. Важно, чтобы это была именно функция. Использование процедуры приведет к ошибке при попытке ее вызова.

<Параметр>, <Параметр>, ... - Произвольные параметры, максимум 8 штук. Это на 1 больше, чем максимальное количество параметров, рекомендуемое стандартами 1С (но если мало, можете добавить).

Параметры соответствуют параметрам функции и служат для создания контекста замыкания.

На этом следует остановиться подробнее.

В 1С нет другого способа передать функции параметры, кроме как через параметры (ваш Кэп). Поэтому контекст замыкания мы тоже передаем через параметры. В определении функции должны быть предусмотрены соответствующие параметры для этого. Значения параметров, переданные при создании функтора, будут в нем инкапсулированы, а затем использованы при вызове функции.

При определении функции рекомендуется в начале списка параметров располагать параметры, которые будут передаваться вызывающей стороной, а в конце списка - параметры контекста замыкания. Соответственно, если функция имеет параметры, передаваемые вызывающей стороной, то при создании функтора мы их можем пропустить, оставив их значения неопределенными. Например, если вызывающая сторона передает 2 параметра, а создающая сторона - еще 2 параметра контекста, создание функтора будет выглядеть так:

Функ = Функтор(<Объект>, <ИмяФункции>, ,, <ПараметрКонтекста1>, <ПараметрКонтекста2>);

 

А вот так происходит вызов функтора:

Результат = Функ.Вызвать(<Параметр>, <Параметр>, ...);

Здесь также можно передать до 8 параметров, они соответствуют все тем же параметрам функции. Но имеют приоритет перед параметрами контекста замыкания: если значение параметра указано и при создании функтора, и при его вызове, то фактически функции будет передано значение, указанное при вызове. Такой подход позволяет при создании функтора определить для параметров значения по умолчанию, которые затем могут быть переопределены вызывающей стороной (по сути, все параметры контекста - это и есть значения по умолчанию).

 

Если функция имеет параметры, передаваемые "по ссылке" (т.е. без Знач), то она может возвращать значения в параметры контекста, инкапсулированные в функторе. Таким образом функция может передавать значения стороне, создавшей функтор (впрочем, их может получать любая сторона, владеющая ссылкой на функтор).

Получать эти значения можно так:

Значение = Функ.Значение(<Индекс>);

<Индекс> - индекс параметра функции в диапазоне [0...7].

Нужно иметь в виду, что значения возвращаемых параметров, используемых вызывающей стороной, попадут не в контекст, а в переменные вызывающей стороны.

 
 Но есть нюанс (ДОПОЛНЕНО)

Есть одна неочевидная особенность. Если в каком-то параметре передать значение Неопределено, то функтор будет считать этот параметр неиспользуемым. Это вынужденная мера, т.к. функтор не знает, сколько в действительности параметров у вызываемой функции. Если функция имеет возвращаемый параметр, и мы передадим в него переменную, содержащую Неопределено, в ожидании, что в эту переменную что нибудь вернется, то нас ждет облом: ничего туда не вернется, т.к. функтор увидит только значение Неопределено и посчитает параметр неиспользуемым. В таких случаях нужно обязательно инициализировать переменную каким-нибудь определенным значением.

ДОПОЛНЕНО. В комментариях tormozit предложил иедю, как купировать эту проблему: в реализации функтора заменить все проверки на Неопределено проверками на Null.

 

Давайте перейдем к примерам практического применения функторов.

 

Пример 1. Ленивые вычисления

Я не буду здесь приводить навязший в зубах пример с бесконечными последовательностями, так любимый функциональщиками. Возьмем что-нибудь, более приближенное к практике 1С.

Пускай у нас есть функция в общем модуле, выполняющая некоторый общий алгоритм, так и назовем ее - ОбщийАлгоритм. Вызывающая сторона должна передать ей в параметре значение. Предположим, что для получения этого значения нужно перелопатить достаточно большой объем данных из базы. Другими словами, вычисление этого значения обходится дорого. Но ОбщийАлгоритм таков, что использует это значение только при определенных условиях. Предположим, что статистически вероятность использования этого значения - 40%. Если мы будем заранее вычислять значение, чтобы передать его в ОбщийАлгоритм, то в 60% случаев мы будем делать это зря.

Решение: оформим тяжелое вычисление в виде функции и будем передавать ее в ОбщийАлгоритм как функтор.

Общий модуль:

Функция ОбщийАлгоритм(КонтейнерЗначения) Экспорт
	//...
	Если УсловиеВыполняется Тогда
		Значение = КонтейнерЗначения.Вызвать();
		//
	КонецЕсли;
	//...
КонецФункции

Вызывающий модуль:

Функция ТяжелаяФункция(Парам1, Парам2) Экспорт
	//...массивные вычисления...
КонецФункции

Процедура ...()

	Результат = Вычисления.ОбщийАлгоритм(Функтор(ЭтотОбъект, "ТяжелаяФункция", Знач1, Знач2));

КонецПроцедуры

В этом примере мы используем функтор как контейнер ленивого вычисления. Все параметры задает создающая сторона, а вызывающая сторона просто получает результат - но только тогда, когда он действительно понадобится.

Обратите внимание, что ТяжелаяФункция объявлена как экспортная, т.к. она должна быть доступна для вызова из другого модуля.

 

Пример 2. Универсальный обход дерева.

Предположим, мы активно работаем с деревом значений. Время от времени нам нужно рекурсивно обходить все узлы дерева и с каждым узлом выполнять некоторое действие. Дерево одно, а действий много и все разные.

Пускай наше дерево содержит, среди прочих, колонки с числами и колонку Пометка (Булево). В примере реализуем несколько операций. Одна будет вычислять максимум значения численной колонки по всем узлам дерева. Вторая будет собирать массив всех узлов, где Пометка = Истина. Третья будет устанавливать значение Пометка во всех узлах.

Начнем с универсальной функции рекурсивного обхода дерева:

// Это универсальная функция, ее можно было бы переместить в общий модуль
Функция ОбходДерева(Узел, Действие)
	Для каждого Строка Из Узел.Строки Цикл
		Действие.Вызвать(Строка);
		ОбходДерева(Строка, Действие);
	КонецЦикла;
	Возврат Действие;
КонецФункции

Функция обхода получает на вход дерево и функтор действия, который применяет к каждому узлу дерева. В качестве значения возвращает тот же функтор действия, это повышает удобство пользования (ниже станет понятно, почему). 

Теперь функции, реализующие действия со строкой дерева:

Функция ДействиеМаксимумПоКолонке(Строка, Колонка, Максимум) Экспорт
	Максимум = Макс(Строка[Колонка], Максимум);
	Возврат Неопределено;
КонецФункции

Функция ДействиеПомеченныеУзлы(Строка, Узлы) Экспорт
	Если Строка.Пометка Тогда
		Узлы.Добавить(Строка);
	КонецЕсли;
	Возврат Неопределено;
КонецФункции

Функция ДействиеУстановитьПометку(Строка, Пометка) Экспорт
	Строка.Пометка = Пометка;
	Возврат Неопределено;
КонецФункции

И, наконец, фронтальные операции:

Функция МаксимумПоКолонке(Дерево, Колонка) Экспорт
	Возврат ОбходДерева(Дерево, Функтор(ЭтотОбъект, "ДействиеМаксимумПоКолонке",, Колонка, 0))
		.Значение(2); // параметр Максимум
КонецФункции

Функция ПомеченныеУзлы(Дерево) Экспорт
	Возврат ОбходДерева(Дерево, Функтор(ЭтотОбъект, "ДействиеПомеченныеУзлы",, Новый Массив))
		.Значение(1); // параметр Узлы
КонецФункции

Процедура УстановитьПометку(Дерево, Пометка) Экспорт
	ОбходДерева(Дерево, Функтор(ЭтотОбъект, "ДействиеУстановитьПометку",, Пометка));
КонецПроцедуры

В этом примере мы используем функтор не для возврата значения вызывающей стороне, а для накопления некоторых данных в контексте замыкания, с последующим их возвратом создающей стороне, либо для модификации переданного параметра.

Пример выглядит тривиальным, но представьте, что таких действий с деревом нужно реализовать десяток. А если нужно при обходе еще и фильтровать узлы дерева по некоторому условию? Которое можно передать универсальной процедуре обхода в виде еще одного функтора. А можно передать функтор условия не процедуре обхода, а функтору действия...

В общем, функторы позволяют реализовывать сложное поведение путем комбинирования его из простых элементов. Способы комбинирования ограничены только вашим воображением.

 

Что под капотом?

Под спойлером реализация функтора, совершенно бесплатно.

 
Вы будете смеяться, но там меньше ста строк

 

 
Стоимость вызова

 

Вступайте в нашу телеграмм-группу Инфостарт

функтор функциональное программирование

См. также

Загрузка и выгрузка в Excel Универсальные функции Программист 1С:Предприятие 8 Россия Бесплатно (free)

Описанный ниже подход позволяет в три шага заполнять формулы в Excel файлы, вне зависимости от ОС сервера (MS Windows Server или Linux). Подход подразумевает отказ от работы с COM-объектом в пользу работы через "объектную модель документа" (DOM).

30.10.2025    4215    Abysswalker    11    

46

Универсальные функции Работа с интерфейсом Программист 1С:Предприятие 8 Бесплатно (free)

Порой необходимо временно отключить расширение 1С, не удаляя его, чтобы не потерять данные. Но в этом случае при каждом запуске всем будет лезть уведомление о неактивном расширении, хотя очевидно, это техническая информация, которой не стоит лишний раз пугать пользователей.

14.05.2025    7828    DeerCven    15    

62

Универсальные функции Программист 1С:Предприятие 8 1C:Бухгалтерия Бесплатно (free)

Благодаря этим пяти строчкам можно больше не заморачиваться с загрузкой из внешних файлов. Пользуюсь везде, всегда и постоянно.

21.05.2024    53223    dimanich70    84    

174

Универсальные функции Программист 1С:Предприятие 8 1C:Бухгалтерия Абонемент ($m)

Задача: вставить картинку из буфера обмена на форму средствами платформы 1С.

1 стартмани

18.03.2024    7709    7    John_d    13    

59

Универсальные функции Программист Стажер 1С:Предприятие 8 1C:Бухгалтерия Бесплатно (free)

Пришлось помучиться с GUID-ами немного, решил поделиться опытом, мало ли кому пригодится.

12.02.2024    67777    atdonya    31    

72

Универсальные функции Программист 1С:Предприятие 8 Бесплатно (free)

На заключительных этапах, когда идет отладка или доработка интерфейса, необходимо много раз переоткрыть внешний объект. Вот один из способов автоматизации этого.

30.11.2023    9671    ke.92@mail.ru    17    

68
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. Трактор 1277 15.03.26 10:08 Сейчас в теме
Любопытные изыскания. Поддерживаю!
Буду думать куда применить.
2. tormozit 7369 15.03.26 10:58 Сейчас в теме
Похожая идея по доработке платформы, но без изменения глобального контекста и потому исключена вероятность конфликтов со старым кодом. Фактические параметры в ней отделены от ссылки на метод.
3. korvintorson 46 15.03.26 11:32 Сейчас в теме
(2) Да, мы недавно касались этой темы на партнерском форуме, в контексте моего предложения по функции НеизвестныйВызов.

У меня есть несколько усовершенствований к вашей идее:
- Вариант метода Выполнить со списком параметров выглядит лишним, если со стороны компилятора обеспечить синтаксис вызова в виде СсылкаМетода(Парам, Парам, ...).
- Полезным будет метод Выполнить(МассивПараметров).

Также стоит подумать, как прикрутить замыкание. Невозможность для создающей стороны передавать свои параметры во многих случаях сводит на нет преимущество функций 1-го класса.

И я не очень понял на счет метода ВыполнитьСтруктурой - как СсылкаМетода сможет сопоставить имена свойств структуры с позициями параметров при вызове метода?
5. tormozit 7369 15.03.26 11:40 Сейчас в теме
(3)
вызова в виде СсылкаМетода(Парам, Парам, ...)

Я старался минимизировать изменения в языке/интерпретаторе/компиляторе и опять же риски конфликтов со старым кодом. А также думал об объеме доработок в сторонних анализаторах кода. Такой вариант вызова все это затрагивает. Для редко используемого элемента синтаксиса такие жертвы выглядят неоправданно.
9. korvintorson 46 15.03.26 12:14 Сейчас в теме
(5) А где тут изменение синтаксиса?
Сравните:
Результат = ИмяФункции(Парам, Парам, ...);
Результат = СсылкаФункции(Парам, Парам, ...);

Синтаксически идентично.
33. korvintorson 46 15.03.26 18:20 Сейчас в теме
(5) Хотя, если вспомнить, что в 1С имена переменных и методов находятся как бы в разных пространствах имен, то да, наверное могут возникнуть проблемы.
28. tormozit 7369 15.03.26 16:48 Сейчас в теме
(3)
Также стоит подумать, как прикрутить замыкание. Невозможность для создающей стороны передавать свои параметры во многих случаях сводит на нет преимущество функций 1-го класса.

Согласен. Это кажется вполне логичным развитием идеи СсылкаМетода и приводит ее по сути к тому, что ты уже описал в этой статье. Дописал про хранение параметров в ссылке метода в свою публикацию. И имя "Функтор" с учетом этого уже лучше подходит чем "СсылкаМетода".
29. tormozit 7369 15.03.26 16:50 Сейчас в теме
(3)
как СсылкаМетода сможет сопоставить имена свойств структуры с позициями параметров при вызове метода?

Ну я же платформе предлагаю это делать, а она вроде бы должна знать, на какой позиции какое имя у параметра.
30. korvintorson 46 15.03.26 16:53 Сейчас в теме
(29) Дело в том, что в 1С имена параметров метода - это элементы его реализации, а не интерфейса. Вынося их наружу, вы создаете внешнюю зависимость от элементов реализации. Уже нельзя будет просто взять и переименовать параметр - снаружи обязательно что-нибудь сломается.
31. tormozit 7369 15.03.26 17:01 Сейчас в теме
(30) Предлагаю не начинать очередной эпизод общего "холивара". Такой способ передачи параметров в некоторых случаях имеет существенные преимущества по сравнению с позиционным. И потому его стоит сразу предусмотреть с рекомендацией "не прибегать к нему без веской причины".
32. korvintorson 46 15.03.26 17:16 Сейчас в теме
(31) При чем здесь холивар? Я же указал на вполне практический момент. Сейчас на уровне интерфейса метода имеют значение только порядковые позиции параметров, но не их имена. Имена мы можем менять в любой момент, не боясь, что сломаются все вызовы. Все это знают, все к этому привыкли.
Вы предлагаете изменить фундаментальную концепцию интерфейса метода, принятую в 1С. Ради чего? Приведите пример.
4. tormozit 7369 15.03.26 11:32 Сейчас в теме
Сама идея хороша. Но в качестве дополнения отмечу, что полный обход объекта ДеревоЗначений намного быстрее делается нерекурсивно и потому отпадает потребность в функторе.
// Формирует массив потомков заданной строки дерева.
// Параметры:
//   СтрокаИлиДеревоЗначений - СтрокаДереваЗначений, ДеревоЗначений - 
// Возвращаемое значение:
//   Массив из СтрокаДереваЗначений - 
Функция ВсеСтрокиДереваЗначенийЛкс(Знач СтрокаИлиДеревоЗначений) Экспорт 
	Если ТипЗнч(СтрокаИлиДеревоЗначений) = Тип("СтрокаДереваЗначений") Тогда
		ДеревоЗначений = СтрокаИлиДеревоЗначений.Владелец();
	Иначе
		ДеревоЗначений = СтрокаИлиДеревоЗначений;
	КонецЕсли; 
	Идентификатор = "_" + СтрЗаменить(Новый УникальныйИдентификатор, "-", "");
	ДеревоЗначений.Колонки.Добавить(Идентификатор);
	МассивСтрок = СтрокаИлиДеревоЗначений.Строки.НайтиСтроки(Новый Структура(Идентификатор, Неопределено), Истина);
	ДеревоЗначений.Колонки.Удалить(Идентификатор); 
	Если ТипЗнч(СтрокаИлиДеревоЗначений) = Тип("СтрокаДереваЗначений") Тогда
		МассивСтрок.Вставить(0, СтрокаИлиДеревоЗначений);  
	КонецЕсли; 
	Возврат МассивСтрок; 
КонецФункции
Показать

Однако есть много других иерархических коллекций (например ДанныеФормыДерево), где такой трюк недоступен и там функтор будет полезен.
7. korvintorson 46 15.03.26 12:05 Сейчас в теме
(4) Это же всего лишь иллюстративный пример. Ну не приводить же мне в пример банальный маппер массива?

А для нерекурсивного обхода дерева у меня есть более красивое решение, дарю:
Процедура СледующийУзелДерева(Знач Дерево, ТекущийУзел) Экспорт 
	
	Если ТекущийУзел = Неопределено Тогда
		ТекущийУзел = Дерево;
	КонецЕсли;
	
	Если ТекущийУзел.Строки.Количество() > 0 Тогда
		ТекущийУзел = ТекущийУзел.Строки[0];
	Иначе
		Пока ТекущийУзел <> Дерево Цикл
			УзелРодитель = ?(ТекущийУзел.Родитель = Неопределено, Дерево, ТекущийУзел.Родитель);
			СледующийИндекс = УзелРодитель.Строки.Индекс(ТекущийУзел) + 1;
			Если СледующийИндекс < УзелРодитель.Строки.Количество() Тогда
				ТекущийУзел = УзелРодитель.Строки[СледующийИндекс];
				Прервать;
			Иначе
				ТекущийУзел = УзелРодитель;
			КонецЕсли; 
		КонецЦикла; 
	КонецЕсли; 

	Если ТекущийУзел = Дерево Тогда
		ТекущийУзел = Неопределено;
	КонецЕсли;
	
КонецПроцедуры
Показать


Используется так:
Узел = СледующийУзелДерева(Дерево);
Пока Узел <> Неопределено Цикл
    //...что-то делаем
    Узел = СледующийУзелДерева(Дерево, Узел);
КонецЦикла;


Но решение на функторах все равно красивее, т.к. не требует каждый раз организовывать однотипный цикл.
Правда, с лямбда-функциями оно было бы еще красивее - не нужно было бы определять лишние функции.

Как думаете, лямбды были бы интересны?
10. tormozit 7369 15.03.26 12:33 Сейчас в теме
(7) Более четко обозначу цель моего дополнения. Цель - выбор оптимального по скорости подхода, а уход от рекурсии - лишь способ ее достижения. Твой второй способ опять же сильно проиграет по скорости холостого обхода предложенному мной обычному циклу.
решение на функторах все равно красивее, т.к. не требует каждый раз организовывать однотипный цикл

Дополительные 3 строки кода на цикл против создания всегда более сложной в восприятии и отделенной от контекста функции для функтора. Я бы сказал что это не рационально, особенно с учетом относительно заметных накладных расходов на вызов динамической компиляции вызова функтора для каждого прохода цикла.

Тонкости замера скорости легковесного кода рекомендую изучить тут https://infostart.ru/1c/articles/1732527/
13. korvintorson 46 15.03.26 12:50 Сейчас в теме
(10)
Цель - выбор оптимального по скорости подхода

Если цель скорость, то да, вы используете платформенный метод НайтиСтроки, он заведомо быстрее любого кода на встроенном языке. Но вот необходимость добавлять и удалять колонку делает решение нишевым.

(10)
Дополительные 3 строки кода на цикл против создания всегда более сложной в восприятии и отделенной от контекста функции для функтора.

Про отделенность от контекста согласен, тут бы лямбда-функция лучше смотрелась.

(10)
Я бы сказал что это не рационально, особенно с учетом накладных расходов на вызов динамической компиляции для каждого прохода цикла

Вот здесь бы и пригодилась ваша СсылкаМетода, она бы позволила избежать динамической компиляции.

А вообще, функторы - это не про скорость, а про чистоту кода и устранение лишних зависимостей. Ну и ленивость опять же, которая местами как раз может дать плюс к производительности существенно больший, чем жалкие микросекунды, потраченные на создание/вызов функтора..
19. tormozit 7369 15.03.26 14:22 Сейчас в теме
(13)
необходимость добавлять и удалять колонку делает решение нишевым

Способ универсален для любого объекта ДеревоЗначений. Можно пример, где способ не работает для ДеревоЗначений?
21. korvintorson 46 15.03.26 14:47 Сейчас в теме
(19) Так в том-то и дело, что он только для дерева значений работает. Сами же написали, что для ДанныеФормыДерево уже не прокатит.
22. tormozit 7369 15.03.26 15:31 Сейчас в теме
(21) Да, я изначально достаточно четко обозначил область применимости этого метода. Поэтому фраза
Но вот необходимость добавлять и удалять колонку делает решение нишевым

рождает подозрение что собеседник не понял этого, т.к. сильно иносказательно написал то же самое, запутывая других читателей. Можно было написать намного проще - "Но применимо только для ДеревоЗначений", хотя и это является избыточным пересказом.
24. korvintorson 46 15.03.26 16:09 Сейчас в теме
(22) Я ни в коем случае не критикую ваше решение, более того - оно мне нравится. Я с уважением отношусь к авторам решений, до которых должен был додуматься сам, но не додумался.
Под нишевостью я имел в виду, что оно не масштабируется на другие виды деревьев. А мой вариант нерекурсивного обхода масштабируется как минимум на ДанныеФормыДерево, и на прочие деревья, где возможен переход к родителю, поэтому мне он кажется более красивым.
16. korvintorson 46 15.03.26 13:15 Сейчас в теме
(10)
Тонкости замера скорости легковесного кода рекомендую изучить тут

Спасибо, очень интересно. Про замедление в режиме отладки я знал, но вот про эффект однострочного кода как-то не задумывался.
12. tormozit 7369 15.03.26 12:47 Сейчас в теме
(7)
Как думаете, лямбды были бы интересны?

Для универсального отбора элементов коллекции я предложил такой подход https://infostart.ru/1c/articles/1887014
14. korvintorson 46 15.03.26 13:05 Сейчас в теме
(12) Скажу прямо, такие решения лучше прятать где-то в недрах. С прагматической точки зрения, если они решают задачу - то и хорошо, но вот в остальном...
17. korvintorson 46 15.03.26 13:22 Сейчас в теме
(12) Можно еще подумать в сторону применения кэша повторного использования для динамически компилируемого кода. Хотя это накладывает ограничения на использование параметров, но в ряде случаев может быть полезным.
25. Трактор 1277 15.03.26 16:12 Сейчас в теме
(7)
Процедура СледующийУзелДерева(Знач Дерево, ТекущийУзел) Экспорт
....
КонецПроцедуры
Узел = СледующийУзелДерева(Дерево, Узел);

Вы, профессор, воля ваша, что-то нескладное придумали! Оно, может, и умно, но больно непонятно. Над вами потешаться будут. (с)
27. korvintorson 46 15.03.26 16:40 Сейчас в теме
(25) Да, какую-то я херню упорол. Процедуру скопипастил из общего модуля, а использование решил на ходу написать, да видно не проснулся еще.

Конечно же, так:
Узел = Неопределено;
СледующийУзелДерева(Дерево, Узел);
Пока Узел <> Неопределено Цикл
	СледующийУзелДерева(Дерево, Узел);
КонецЦикла; 

Но это, наверное, знак, что надо таки переделать процедуру в функцию, ибо оно прям напрашивается подсознательно.
Трактор; +1 Ответить
6. tormozit 7369 15.03.26 12:02 Сейчас в теме
Если в каком-то параметре передать значение Неопределено, то функтор будет считать этот параметр неиспользуемым

Я в похожих механизмах уже давно использую Null, т.к. это значение значительно реже используется во встроенном языке.
8. korvintorson 46 15.03.26 12:10 Сейчас в теме
(6) О, это вы хорошую идейку подкинули! Утащу к себе.
11. aximo 2605 15.03.26 12:34 Сейчас в теме
Если притянуть «за уши» ооп в 1с, то данные подходы заложены в моделях менеджеров создаваемых объектов ….
15. korvintorson 46 15.03.26 13:07 Сейчас в теме
(11) А подробнее можете развернуть? Модуль менеджера - это по сути тот же общий модуль, статический.
18. aximo 2605 15.03.26 13:34 Сейчас в теме
(15) создаешь объект (документ, справочник….)

В модуле менеджера этого объекта - прописываешь процедуры или функции…

Далее

Новыйспр = спр.мойспр.создать()

Новыйспр.мойметод()

Общий модуль - это обособленная структура, не относится к каким-либо создаваемым объектам, а скорее, ко всем….
20. korvintorson 46 15.03.26 14:44 Сейчас в теме
(18) Вы наверное модуль объекта имели в виду?
23. tormozit 7369 15.03.26 15:50 Сейчас в теме
Предлагаю упростить получение результата группового выполнения функтора, т.к. обращение по индексу к выходному параметру слишком опасно и неудобно. Вот мой вариант (добавил свойство "Результат"):
1. Модуль обработки "Функтор"
// Не рекомендуется использовать в часто выполняющемся коде
// Идея https://infostart.ru/1c/articles/2639512

#Если Сервер ИЛИ ВнешнееСоединение ИЛИ ТолстыйКлиентОбычноеПриложение Тогда

Перем Результат Экспорт; // Произвольный - результат последнего вызова
Перем мОбъект; // Произвольный
Перем мИмяФункции; // Строка
Перем мПараметрыКонструктора; // Массив
Перем мМаксИндексПараметра; // Число

Функция Создать(Знач Объект, Знач ИмяФункции, П0 = Null, П1 = Null, П2 = Null, П3 = Null, П4 = Null, П5 = Null, П6 = Null, П7 = Null, П8 = Null, П9 = Null) Экспорт
	мОбъект = Объект;
	мИмяФункции = ИмяФункции;
	мПараметрыКонструктора = Новый Массив(мМаксИндексПараметра + 1);
	мПараметрыКонструктора[0] = П0;
	мПараметрыКонструктора[1] = П1;
	мПараметрыКонструктора[2] = П2;
	мПараметрыКонструктора[3] = П3;
	мПараметрыКонструктора[4] = П4;
	мПараметрыКонструктора[5] = П5;
	мПараметрыКонструктора[6] = П6;
	мПараметрыКонструктора[7] = П7;
	мПараметрыКонструктора[8] = П8;
	мПараметрыКонструктора[9] = П9;
	Возврат ЭтотОбъект;
КонецФункции

// Выходные значения параметров не возвращаются напрямую. Для этого нужно вызывать 
Функция Вызвать(П0 = Null, П1 = Null, П2 = Null, П3 = Null, П4 = Null, П5 = Null, П6 = Null, П7 = Null, П8 = Null, П9 = Null) Экспорт
	ПараметрыВызова = Новый Массив(мМаксИндексПараметра + 1);
	ПараметрыВызова[0] = П0;
	ПараметрыВызова[1] = П1;
	ПараметрыВызова[2] = П2;
	ПараметрыВызова[3] = П3;
	ПараметрыВызова[4] = П4;
	ПараметрыВызова[5] = П5;
	ПараметрыВызова[6] = П6;
	ПараметрыВызова[7] = П7;
	ПараметрыВызова[8] = П8;
	ПараметрыВызова[9] = П9;
	ТекстПаметров = "";
	Запятые = "";
	Для Н = 0 По мМаксИндексПараметра Цикл
		Если ПараметрыВызова[Н] <> Null Тогда
			ТекстПаметров = ТекстПаметров + Запятые + "П" + Н;
			Запятые = ",";
		ИначеЕсли мПараметрыКонструктора[Н] <> Null Тогда
			ТекстПаметров = ТекстПаметров + Запятые + "мПараметрыКонструктора[" + Н + "]";
			Запятые = ",";
		Иначе
			Запятые = Запятые + ",";
		КонецЕсли;
	КонецЦикла;
	Вызов = "мОбъект." + мИмяФункции + "(" + ТекстПаметров + ")";
	Результат = Вычислить(Вызов);
	Возврат Результат;
КонецФункции

// Выходное значение параметра по индексу.
Функция Параметр(Знач Индекс) Экспорт
	Возврат мПараметрыКонструктора[Индекс];
КонецФункции

мМаксИндексПараметра = 9;
#КонецЕсли
Показать

2. Тест для консоли кода ИР
Функция Главный()
	// Тестовые данные
	Дерево = Новый ДеревоЗначений;
	Дерево.Колонки.Добавить("Сумма"); // Число - неотрицательное
	Дерево.Колонки.Добавить("Пометка");
	Корневая = Дерево.Строки.Добавить();
	Корневая.Сумма = 1;
	Дочерняя = Корневая.Строки.добавить();
	Дочерняя.Сумма = 3;
	Дочерняя.Пометка = Истина;
	Корневая.Строки.добавить().Сумма = 2;
	
	// Тест
	Максимум = МаксимумПоКолонке(Дерево, "Сумма");
	Помеченные = ПомеченныеУзлы(Дерево);
КонецФункции

Функция МаксимумПоКолонке(Дерево, ИмяСвойства) Экспорт
	Функтор = НовыйФунктор(ЭтотОбъект, "ДействиеМаксимумПоСвойству",, ИмяСвойства, 0);
	Возврат ОбходДерева(Дерево, Функтор).Результат;
КонецФункции

Функция ПомеченныеУзлы(Дерево) Экспорт
	Функтор = НовыйФунктор(ЭтотОбъект, "ДействиеПомеченныеУзлы",, Новый Массив);
	Возврат ОбходДерева(Дерево, Функтор).Результат;
КонецФункции
	
// Функторы

Функция ДействиеМаксимумПоСвойству(Строка, ИмяСвойства, Максимум) Экспорт
	Максимум = Макс(Строка[ИмяСвойства], Максимум);
	Возврат Максимум;
КонецФункции  

Функция ДействиеПомеченныеУзлы(Строка, Помеченные) Экспорт
	Если Строка.Пометка = Истина Тогда
		Помеченные.Добавить(Строка);
	КонецЕсли;
	Возврат Помеченные;
КонецФункции

// Служебные

Функция ОбходДерева(Узел, Функтор)
	Для каждого Строка Из Узел.Строки Цикл
		Функтор.Вызвать(Строка);
		ОбходДерева(Строка, Функтор);
	КонецЦикла;
	Возврат Функтор;
КонецФункции

Функция НовыйФунктор(Объект, ИмяФункции, _П0 = Null, _П1 = Null, _П2 = Null, _П3 = Null, _П4 = Null, _П5 = Null, _П6 = Null, _П7 = Null, _П8 = Null, _П9 = Null) Экспорт
	Возврат Обработки.Функтор.Создать().Создать(Объект, ИмяФункции, _П0, _П1, _П2, _П3, _П4, _П5, _П6, _П7, _П8, _П9);
КонецФункции
Показать
26. korvintorson 46 15.03.26 16:26 Сейчас в теме
(23) Согласен, так удобнее, утащу. Но экспортная переменная мне не нравится, лучше добавить функцию Результат().
Правда, если нужно возвращать создающей стороне больше одного значения, то все равно придется обращаться по индексу. Ну или возвращать структуру в Результат.
37. пользователь 15.03.26 21:54
Сообщение было скрыто модератором.
...
34. gybson 6 15.03.26 20:44 Сейчас в теме
Я не уловил отличие от всех остальных реализаций. Типа длительных операций, отложенных операций, обработчиков конвертации и еще тонны примеров с "Выполнить". Ну т.е. вот конкретный ОбщегоНазначения.ВыполнитьМетодКонфигурации чем не угодил?
35. korvintorson 46 15.03.26 21:17 Сейчас в теме
(34) Ну, почему же не угодил? Нормальная процедура. А при чем здесь она?
36. gybson 6 15.03.26 21:45 Сейчас в теме
(35) Аккуратнее как-то она.

Вы если хотите контекст использовать текущий, так сразу по месту и делайте "Выполнить". А так получился просто извращенный вызов.

В том же питоне можно функцию объявить внутри функции даже. Есть декораторы и прочее. Ну смысл вот сломать себе ногу, чтобы быть похожим на артиста?
38. korvintorson 46 15.03.26 22:00 Сейчас в теме
(36) Вам наверняка нужно прочитать раздел с теорией.
Для отправки сообщения требуется регистрация/авторизация