Функциональное программирование в 1С

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

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


&НаКлиенте
Процедура запуск(Команда)

ТекстСообщения = "Результат вычисления с функцией delta = ";

//вычислим значение константы с дельтой N*5
deltaN5 = ВычислитьЗначениеСДельтой(Константа, newFunction("deltaN5", Новый Структура("N", N)));
Сообщить(СтрЗаменить(ТекстСообщения,"delta","deltaN5") + Строка(deltaN5));
//Результат вычисления с функцией deltaN5 = 85

//вычислим значение константы с дельтой N*M
deltaNM = ВычислитьЗначениеСДельтой(Константа, newFunction("deltaNM", Новый Структура("N, M", N, M)));
Сообщить(СтрЗаменить(ТекстСообщения,"delta","deltaNM") + Строка(deltaNM));
//Результат вычисления с функцией deltaNM = 55

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

//БЛОК ОСНОВНОЙ ПРОГРАММЫ

// Функция принимает в качестве аргументов значение константы и функцию вычисления дельты
//         функция выступает в роли функции высшего порядка
//
&НаКлиенте
Функция ВычислитьЗначениеСДельтой(Константа, functionDelta)

//вычисление дельты, вызов функции
Delta = callFunction(functionDelta);

//основное вычисление
Результат = Константа + Delta;

Возврат Результат;

КонецФункции // ПеремножитьЧисла()

// Функция умножает переданное в аргументе значение на 5
//         функция выступает в роли функции первого класса
//
&НаКлиенте
Функция deltaN5(N)

возврат N*5;

КонецФункции // ()

// Функция перемножает значения переданные в аргументах
//         функция выступает в роли функции первого класса
//
&НаКлиенте
Функция deltaNM(N, M)

возврат N*M;

КонецФункции // ()

//БЛОК ВСПОМОГАТЕЛЬНЫХ ФУНКЦИЙ

// вспомогательная функция для инициации новой функции
//
// Параметры:
//  funcName  - Строка - Имя функции
//  arguments  - Структура - структура содержащая аргументы вызываемой функции
//                 аргументы должны быть расположены в том же порядке как в вызываемой функции
//
// Возвращаемое значение:
//   Структура   - набор содержащий имя функции и ее аргументы
//
&НаКлиенте
Функция newFunction(funcName, arguments)

newFunction = Новый Структура("funcName, arguments", funcName, arguments);

Возврат newFunction;

КонецФункции // newFunction()

// вспомогательный модуль вызова функций
//
// Параметры:
// functionSet - Структура - Структура содержащая набор элементов для вызываемой функции
//  funcName  - Строка - Имя вызываемой функции
//  arguments  - Структура - структура содержащая аргументы вызываемой функции
//     аргументы должны быть расположены в том же порядке как в вызываемой функции
//
// Возвращаемое значение:
//   Произвольный - возвращаемое значение вызываемой функции
&НаКлиенте
Функция callFunction(functionSet)

result = Неопределено;

argumentsString = "";
Для каждого argument Из functionSet.arguments Цикл
argumentsString = argumentsString+"functionSet.arguments."+argument.Ключ+","; //раскладываем аргументы в строку
КонецЦикла;

argumentsString = Лев(argumentsString, СтрДлина(argumentsString)-1);

СтрокаДляВыполнения = СтрЗаменить("result = funcName(arguments)", "funcName", functionSet.funcName);
СтрокаДляВыполнения = СтрЗаменить(СтрокаДляВыполнения, "arguments", argumentsString);

Выполнить(СтрокаДляВыполнения);

Возврат result;

КонецФункции // func()

Обработка тестировалась на 1С:Предприятие 8.3 (8.3.13.1513)

25 Comments

  1. ifal

    Как-то уже писали об этом https://infostart.ru/public/591496/.

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

    Reply
  2. Darklight

    (1)Согласен! Прелести функционального программирования именно в:

    1. Декларативности (ну в большей степени чем классический императивный подход)

    2. Развёртке кода (когда компилятор условно декларативные инструкции разворачивает в последовательность простых инструкций, попутно проводя оптимизацию, практически без падения производительности; заодно и распараллеливая выполнение при возможности)

    Это позволяет писать алгоритмы достаточно компактно и, зачастую, более понятно (по времени на анализ), чем в императивном стиле (тем более в процедурном).

    Эмуляция же этого процесса:

    Во-первых, всё-равно будет выглядеть коряво (а не так красиво как это даёт истинный функциональный стиль)

    Во-вторых, никакой оптимизиации тут не жди. Та же рекурсия, которая при функциональном программировании почти всегда разворачивается хотя бы в циклы (или даже просто в линейный поток команд, когда число итераций на момент запуска известно и не велико), то тут постоянный вызов операции «Выполнить» рано или поздно заполнит стек, не говоря уже о том, что код постоянно будет компилироваться в рантайме — а это время.

    В-третьих, это замечание к операции «Выполнить» — она не поддерживается под iOS и в WEB-Клиенте, а если заменить её на «Вычислить» — всё равно не будет поддерживаться на iOS. Частично, вероятно (сам не проверял) обойти можно заменив её на «ВыполнитьОбработкуОповещения» — но тут тоже свои нюансы, как минимум, будет недоступно в серверном контексте.

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

    Reply
  3. alexey.kutya

    (1) спасибо за ссылку, оказывается есть единомышленники ))

    да, это эмуляция, но это работает

    используется не для частной задачи

    никто не мешает добавить вложенности функций

    Reply
  4. alexey.kutya

    (2) понятно, что коряво, и ограничения в применении есть, но язык 1С не повзоляет большего (

    Reply
  5. Darklight

    (4)Вы просто не умеете его готовить. И не только сам язык, но и саму платформу — я дописал свой комментарий, который случайно разместил недописанным. Там, в конце я указал про кодогенерацию — это мощная штука и это вполне реализуемо — коли надо.

    Но вообще-то, от 1С больше всего люди ждут хотя бы возможности вызывать функции, по ссылке на них (например переданной через аргументы другой функции) — что, в общем-то, вполне является атрибутом классического процедурного стиля программирования (без всяких там функций высших порядков, кэрринга и лямбда), но этого до сих пор нет в 1С (ну на клиенте это можно эмулировать через паттерн callback (я об этом написал), а вот на сервере — никак — только через «Вычислить», но серверный контекст мобильного приложения на iOS вообще никак не может позволить выполнять функции с не статически прописанными вызовами прямо в алгоритме).

    Да и, в любом случае, функция должна быть экспортной — а в 1С функции по умолчанию приватные — что очень сильно мешает. Как и куча заморочек с контекстом выполнения (серверный/клиентский) с невозможность по-человечески его выбирать при вызове функций!

    Reply
  6. alexey.kutya

    (5)

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

    я в принципе это и реализовал

    Reply
  7. alexey.kutya

    (5)

    Там, в конце я указал про кодогенерацию — это мощная штука и это вполне реализуемо — коли надо

    Написать свой интерпретатор? Или я не так понял?

    Reply
  8. Darklight

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

    Reply
  9. alexey.kutya

    (8)тогда скорее всего это будет транслятор. Хорошо, мы транслируем наш код в код 1С, и затем мы его должны каким-то образом выполнить, кроме как использовать тот же Выполнить() на ум ничего не приходит.

    А если транслировать в байт-код, то как его отправить в стек выполнения? Может быть у вас есть информация на эту тему? Было бы интересно.

    Reply
  10. yarsort

    Как по мне, вещь бестолковая.

    Reply
  11. Darklight

    (9)Транслятор так транслятор (хотя это всё-таки кодогенератор или компилятор — смотря что на выходе). В первую очередь я имел в виду статическую трансляцию — динамическая нужна реже.

    Хотя динамически тоже возможно — можно динамически создавать внешние обработки и использовать их в runtime — извращение ещё то — но это вполне реально.

    Ну, или, если транслировать в язык 1С- то вызвать «Вычислить» однократно — это не тоже самое, что вызывать «Вычислить» сотни раз в секунду — ну Вы поняли, надеюсь, что я имею в виду.

    На iOS только всё-равно не будут работать все динамические способы — таковы ограничения ОС.

    Но, в любом случае, это всё извращение. Я Вас к этому не «подбиваю». Это скорее просто развлечение и разминка для ума, чем практическая задача.

    Reply
  12. alexey.kutya

    (10)

    Как по мне, вещь бестолковая.

    согласен, каждому свое

    Reply
  13. ValeriVP

    мне кажется не будет работать. параметры в структуре могут поменяться местами и будет облом.

    но идея может заслуживать внимания.

    поэтому мой вариант (не проверял на работоспособность):

    #Область ПомогаторыРаботыСФунциямиВВидеПараметров
    
    Функция НоваяФункция(ИмяФункции, Параметр1 = «_NaN_», Параметр2 = «_NaN_», Параметр3 = «_NaN_», Параметр4 = «_NaN_»,
    Параметр5 = «_NaN_», Параметр6 = «_NaN_», Параметр7 = «_NaN_», Параметр8 = «_NaN_»,
    Параметр9 = «_NaN_», Параметр10 = «_NaN_», Параметр11 = «_NaN_», Параметр12 = «_NaN_»,
    Параметр13 = «_NaN_», Параметр14 = «_NaN_», Параметр15 = «_NaN_», Параметр16 = «_NaN_») Экспорт
    ПустойПараметр = «_NaN_»;
    СтрокаПараметров = «»;
    СтрокаПустыхПараметров = «»;
    МассивПараметров = Новый Массив;
    Для н = 1 по 16 Цикл
    ЗначениеПараметра = Вычислить(«Параметр» + н);
    МассивПараметров.Добавить(ЗначениеПараметра);
    Если ЗначениеПараметра = ПустойПараметр тогда
    СтрокаПустыхПараметров = СтрокаПустыхПараметров + «, «;
    Иначе
    СтрокаПараметров = СтрокаПараметров + СтрокаПустыхПараметров + СтрШаблон(«, Параметры[%1]», н — 1);
    СтрокаПустыхПараметров = «»;
    КонецЕсли;
    КонецЦикла;
    СтрокаПараметров = Сред(СтрокаПараметров, 3);
    ДанныеФункции = Новый Структура;
    ДанныеФункции.Вставить(«МассивПараметров», МассивПараметров);
    ДанныеФункции.Вставить(«СтрокаВызова», СтрШаблон(«%1(%2)», ИмяФункции, СтрокаПараметров));
    
    Возврат ДанныеФункции;
    
    КонецФункции
    
    Функция ВызватьФункцию(ДанныеФункции) Экспорт
    Параметры = ДанныеФункции.МассивПараметров;
    Возврат Вычислить(ДанныеФункции.СтрокаВызова);
    КонецФункции
    
    #КонецОбласти
    
    

    Показать

    Использовать:

    ДанныеФункции = НоваяФункция(«МойМодуль.МояЭкспортнаяФункция», Параметр1, ,,,, «ЕщеПараметр», ,,, «НуИЕще»);
    ВызватьФункцию(ДанныеФункции)
    
    Reply
  14. alexey.kutya

    (14) работать будет, но с условием что структура куда помещаются параметры не будет меняться

    Ваша реализация тоже интересная, я попробую ее на следующей неделе

    Reply
  15. ValeriVP

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

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

    Reply
  16. alexey.kutya

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

    Reply
  17. alexey.kutya

    (14) ваш вариант рабочий, только поменял имя переменной на ПараметрыФункции (Параметры зарезервировано для формы)

    Функция ВызватьФункцию(ДанныеФункции) Экспорт
    ПараметрыФункции = ДанныеФункции.МассивПараметров;
    Возврат Вычислить(ДанныеФункции.СтрокаВызова);
    КонецФункции
    
    Reply
  18. alexey.kutya

    (14) Ваш вариант поудобнее будет.

    Если не против, я заменю в публикации на ваш код?

    Reply
  19. ValeriVP

    (19) да пожалуйста

    Reply
  20. ValeriVP

    (18) на клиенте это не нужно — можно (и ИМХО нужно) использовать обработку оповещения

    Reply
  21. alexey.kutya

    (21) но в этом случае не будет эффекта callback функции, результат выполнения не вернется в точку вызова

    Reply
  22. so-quest

    (8) Года 4 назад было реализовано такое. Взяли Haxe и сгенерировали код на 1С

    Reply
  23. Darklight

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

    Reply
  24. so-quest

    (24) Смотри — http://yoksel.net.ru/haxe1s

    Вообще чисто функциональные языки в 1С — вредны с моей точки зрения. Ну или нужен некий компромисс как C#+F#

    Reply
  25. Darklight

    (25)За ссылку спасибо.

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

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

    Hexe1s -хорош как «другой подход». Но…. чтобы это получило распространение…. нужно очень активное продвижение и использование в проектах! А это, почти не реально, если это не будет хот бы поощрять сама компания 1С — а она это делать не будет.

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *