Реактивное программирование для чайников

  1. 2 г. назад
    27.08.2022 12:26:15 отредактировано sda553

    Концепция реактивного программирования сильно вводит в ступор программистов, привыкших к традиционному императивному программированию.
    Лично у меня накопился солидный опыт реактивного программирования. Я много раз пытался написать какую-то учебную статью вида «Реактор для чайников», но все спотыкался, как же попроще объяснить вещи и при этом не пропустить какую-либо неточность. И вот примерный результат для Вашего внимания.
    Допустим у нас есть традиционный алгоритм приготовления яичницы.
    1. Если нет дома яиц, послать жену в магазин за яйцами. Иначе перейти на п.3
    2. Дождаться жену с яйцами.
    3. Разогреть сковороду с маслом.
    4. Разбить пару яиц на сковороду
    5. Если дома нет соли, послать жену за солью. Иначе перейти на п7.
    6. Дождаться жену с солью
    7. Посолить яйца.
    8. Жарить до готовности
    9. Подать яичницу на стол
    Это императивный традиционный алгоритм. Его недостаток в том, что он синхронный и блокирующий. Так, в п.2, п6, п8 идет ожидание, блокирующее выполнение программы до момента пока не произойдет какое-то событие. Конечно, можно использовать какую-то многопоточность, делать в соседнем потоке другие полезные дела. Но это лишь способ, который все равно не меняет суть или концепцию императивной программы.
    Суть реактивной программы, это не писать действия программы последовательно одна за другой, а писать императивные кусочки действий, которые надо выполнить по какому-то событию или сигналу. В этом случае нам не нужно даже заботиться о том, чтобы открывать/закрывать потоки и тому подобной ерунде. Программный движок, обнаружив, что у вас реактивный код, сам выделит оптимальное число потоков, чтобы выполнять ваши кусочки в оптимальном, на его взгляд, порядке, максимально снижая простой программы.
    Итак, чтобы сделать программу реактивной, нам надо выделить действия и выделить сигналы, по которым эти действия должны запуститься. Так как в реактивном программировании не важен порядок действий, то давайте начнем с конца.
    ==========
    ris1.jpg

    ==========
    Да, вот так выглядит компьютерная программ в концепции реактивного программирования. Да, смотрится неудобно, а отладка вообще кошмар. Но зато движок может сам решить какие действия ему исполнять, когда ему это делать (естественно по наличию сигнала), а также может взять на выполнение сколько угодно этих действий одновременно, максимально оптимизируя использование ядер.
    Это была простая программа, но, по сути, это уже реактивная программа. Настоящая реактивная программ может состоять из описания тысяч таких действий и сигналов. И каждое из этих действий может быть исполнено в любом неизвестном заранее порядке, и одновременно с какими-либо другими действиями.
    Но мы видим и составные части программного кода. Это сигнал, по которому надо стартовать действие. Это само действие, которое надо исполнить, и, наконец, это выходные сигналы, которые генерирует данное действия. Эти элементы обязательны, но с оговорками.
    Например. Действие должно стартовать по [Сигнал А] И [Сигнал Б]. В этом случае данное действие не будет стартовать пока не просигнализируют оба эти сигнала. В любом порядке, но оба этих сигнала должны поступить.
    То же самое и про действия, которые могут стартовать по [Сигнал А] ИЛИ [Сигнал Б]. Думаю, объяснять разницу не надо.
    Второе – один сигнал, не означает, что по нему может стартовать только одно действие. Несколько действий могут использовать для старта один и тот же сигнал.
    Ну и то, что действие может генерировать не один, а разные сигналы на выходе, в зависимости от каких-то условий, мы видели на примере действий [Проверить наличие яиц.] и [Проверить наличие соли]
    Попробуем оптимизировать нашу реактивную программу. Мы можем «одновременно» проверить наличие и яиц и соли, и послать жену в магазин и за солью, и за яйцами один раз. При этом мы можем начать разогревать сковороду, не дожидаясь жены из магазина.
    Тогда программа будет такая. Для удобства напишу ее в хронологическом порядке, но этот порядок не важен в реактивном программировании:
    ===========
    ris2.jpg

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

    Ответы: (13) (16) (17)
  2. Ну такое. Наличие яиц не обозначает что их сразу нужно жарить, можно же ещё миллион блюд сделать.

    Т.е. для уточнения результата нужно ещё миллион классических программ.

    Плюс, ладно соль, ее можно покупать отдельным потоком. А вот яйца купишь лишние - они со временем испортятся

  3. 27.08.2022 12:42:07 отредактировано sda553

    Ну, а если вы не чайник, или вы заинтересовались концепцией, то дальнейшее более строгое чтиво тут
    https://habr.com/ru/company/oleg-bunin/blog/545702/

    Ответы: (16)
  4. Изложено хорошо.

  5. Реактивное программирование подарило нам новый вид тормозов: ресурсов на сервере полно, а программа тупит.

  6. 27.08.2022 14:48:49 отредактировано sda553

    (6) ээээ . Не путать с React.js

  7. 27.08.2022 15:06:04 отредактировано sda553

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

    Ответы: (11)
  8. Я не совсем понял. Яичницу нужно солить если обнаружена соль. Но если яичница посолена, то все равно нужно солить, пока не закончится соль чтоли? А когда закончится, то послать жену за солью, и потом снова солить? И так бесконечно, пока в магазине не закончится соль? Где условие прекращения соления?

    Ответы: (10)
  9. webdev Но если яичница посолена, то все равно нужно солить

    Нет, на событие "яичница посолена" там другое действие. См.внимательнее.

    Ответы: (12)
  10. 27.08.2022 22:24:33 отредактировано webdev

    sda553 Нет, на событие "яичница посолена" там другое действие. См.внимательнее.

    Да, это действие тоже есть. Но оно не противоречит другому действию: "Обнаружена соль". Действия же не блокируют друг друга, могут выполняться параллельно?
    Или нужно расставить приоритеты, какое действие важнее, и все более низкие отменяются?

    Ответы: (14)
  11. sda553 Заметили, насколько оптимизировалась программа?

    Это обычная программа с вызовом асинхронных подпрограмм. Сигнал - это вызов подпрограммы. Действие - это подпрограмма. Действие может издавать сигналы, то есть подпрограмма может вызывать другие подпрограммы. То же самое можно написать на JS.

  12. (12) ты идиот. уволься нахер, не позорь профессию

    Очевидно что реакция на событие снимает флаг

    Ответы: (18)
  13. Вот вам реактивное программирование - защищает русских фашистов - уволить за идиотизм

  14. sda553 Так, в п.2, п6, п8 идет ожидание, блокирующее выполнение программы до момента пока не произойдет какое-то событие.

    Часто такое трудно обойти. Например, нельзя выбрать сковородку пока неизвестно сколько яиц принесет жена.
    Завтра почитаю, что там в (3) ...

    Ответы: (19)
  15. (0) тебе лучше с Теслой посоветоваться.
    Но он счас занят. Потерял сенсея. Ищет нового.

  16. 28.08.2022 04:03:05 отредактировано webdev

    PR Очевидно что реакция на событие снимает флаг

    Где это написано, идиот?
    По-твоему так получается:
    Если есть соль, то солим. Флаг наличия соли сразу снимается. Соли больше нет, якобы. Срабатывает триггер что нужно послать жену в магаз за солью. Соль появляется, срабатывает триггер появления соли. Снова солим. Снова снимается флаг наличия соли. Снова покупаем соль. И так дома тонна соли в запасниках, и пересоленная яичница.

    Ответы: (21) (22)
  17. Bumer Например, нельзя выбрать сковородку пока неизвестно сколько яиц принесет жена.

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

  18. Ассинхронность, обратные вызовы, длительные операции, а с 8.3.18 еще и "Обещание". Это 1С. А что аффтар мне навесит хочет - это лапша.

  19. 28.08.2022 09:44:44 отредактировано sda553

    (18) ты путаешь сигнал "соли нет" и состояние "соли нет".
    По сигналу, или совокупности сигналов одно подписанное на этот сигнал действие, в одном и том же контнксте, стартует все таки один раз

    Ответы: (22)
  20. sda553 (18) ты путаешь сигнал "соли нет" и состояние "соли нет".
    По сигналу, или совокупности сигналов одно подписанное на этот сигнал действие, в одном и том же контнксте, стартует все таки один раз

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

  21. Эй, сда, так нахрена этот язык программирования? Какой в нем плюс?

  22. Это не язык, а парадигма. Писать в таком стиле можно на разных языках, в том числе 1с.

    Ответы: (26)
  23. ++ В теории более эффективно использует процы, параллельные алгоритмы могут (но это не точно) выполняться быстрее.
    -- Код превращается в говнокод, плохо читаемый, отлаживаемый и понимаемый. Писать в таком стиле дольше и труднее.

    Абсолютное большинство написанного так кода, по моим наблюдениям, никаких преимуществ не дает. Тупые попытки переписать асинхронно алгоритмы, в принципе не годные для этого. Единственный осязаемый результат - повышения порога вхождения в 1с.

  24. 29.08.2022 17:45:03 отредактировано webdev

    ТеньД Это не язык, а парадигма. Писать в таком стиле можно на разных языках, в том числе 1с.

    Не, 100% язык под это есть специальный. Я недавно изучал его, программировал не нем. Тупость полнейшая. Неудобно. Лисп или Кобол называется, точно не помню. Но он реально больше только запутывает, чем помогает.

    Ответы: (28)
  25. Как на 1С можно это написать? Я не представляю. Могу представить только как написать асинхронно, обычным способом. В 1С же есть теперь обещание? С ними было бы очень удобно написать программу по приготовлению яичницы. Они для этого и созданы, чтобы параллельно запускать несколько процессов, и чтобы было красиво.
    Как это может ускорить параллельные алгоритмы - не понятно. Скорее всего никак. Они как выполнялись, так и выполняются. Разница только в красоте кода, и больше ни в чем.

    Ответы: (28)
  26. webdev Не, 100% язык под это есть специальный.

    Специальный язык наверное было бы неплохо. Обычные языки и IDE под них все же не заточены под новую метлу, работать неудобно.

    webdev Как на 1С можно это написать? Я не представляю.

    Вот так

    // Вызывает диалог выбора каталога.
    // 
    // Параметры:
    //     Форма - ФормаКлиентскогоПриложения - вызывающий объект.
    //     ПутьКДанным          - Строка             - полное имя реквизита формы, содержащего текущее значение каталога.
    //                                                 Например.
    //                                                "РабочийКаталог" или "Объект.КаталогИзображений".
    //     Заголовок            - Строка             - заголовок для диалога.
    //     СтандартнаяОбработка - Булево             - для использования в обработчике "ПриНачалаВыбора". Будет заполнено
    //                                                 значением Ложь.
    //     ОповещениеЗавершения - ОписаниеОповещения - вызывается после успешного помещения нового значения в реквизит.
    //
    Процедура ВыбратьКаталог(Знач Форма, Знач ПутьКДанным, Знач Заголовок = Неопределено, СтандартнаяОбработка = Ложь, ОповещениеЗавершения = Неопределено) Экспорт
    	
    	СтандартнаяОбработка = Ложь;
    	
    	Контекст = Новый Структура;
    	Контекст.Вставить("Форма", Форма);
    	Контекст.Вставить("ПутьКДанным", ПутьКДанным);
    	Контекст.Вставить("Заголовок", Заголовок);
    	Контекст.Вставить("ОповещениеЗавершения", ОповещениеЗавершения);
    	
    	ОповещениеПродолжения = Новый ОписаниеОповещения(
    		"ВыбратьКаталогЗавершениеОтображенияДиалогаВыбораФайла", 
    		ЭтотОбъект, Контекст);
    		
    	ФайловаяСистемаКлиент.ВыбратьКаталог(ОповещениеПродолжения, Контекст);
    	
    КонецПроцедуры
    
    Процедура ВыбратьКаталогЗавершениеОтображенияДиалогаВыбораФайла(Каталог, ДополнительныеПараметры) Экспорт
    	
    	Если Не ПустаяСтрока(Каталог) Тогда
    		
    		ДополнительныеПараметры.Форма[ДополнительныеПараметры.ПутьКДанным] = Каталог;
    		
    		Если ДополнительныеПараметры.ОповещениеЗавершения <> Неопределено Тогда
    			ВыполнитьОбработкуОповещения(ДополнительныеПараметры.ОповещениеЗавершения, Каталог);
    		КонецЕсли;
    		
    	КонецЕсли;
    	
    КонецПроцедуры
    

    Столько [...] чтобы юзер выбрал папку. И главное нахуа этот цирк?

    Ответы: (29) (30) (36)
  27. ТеньД не заточены под новую метлу

    Это довольно старинная метла.

  28. ТеньД Вот так

    Это типа подписки на события? Это же не то. Как ты подпишешься сразу на два события, типа "сковородка разогрета, и обнаружены яйца"? Тут обещания удобно использовать.
    Сковородка может быть уже давно разогрета, сигнал поступил уже давно, а сигнал что яйца обнаружены поступит позже. То что сковорода разогрета, к тому времени будет уже не сигнал, а состояние.

    Ответы: (32)
  29. 29.08.2022 18:30:16 отредактировано webdev
    (({ яицНет, солиНет }) => {
    
    
        Promise.all([
            проверитьСоль(),
            проверитьЯйца(),
            разогретьСковороду(),
        ]).then(([ ожиданиеСоли ]) =>
            разбитьЯйца()
            .then(ожиданиеСоли)
            .then(посолитьЯичницу)
            .then(жаритьЯичницу)
            .then(податьЯичницу),
        );
    
        function разогретьСковороду() {
            return делатьДолго('разогретьСковороду', 100);
        }
    
        function проверитьЯйца() {
            return яицНет ? делатьДолго('купитьЯйца', 200) : сделать('проверитьЯйца');
        }
    
        function проверитьСоль() {
            const ожиданиеСоли = солиНет ? делатьДолго('купитьСоль', 200) : сделать('проверитьСоль');
    
            return Promise.resolve(() => ожиданиеСоли);
        }
    
        function разбитьЯйца() {
            return сделать('разбитьЯйца');
        }
    
        function посолитьЯичницу() {
            return сделать('посолитьЯичницу');
        }
    
        function жаритьЯичницу() {
            return сделать('жаритьЯичницу');
        }
    
        function податьЯичницу() {
            return сделать('податьЯичницу');
        }
    
    
        //  Вспомогательные функции
    
        function сделать(действие) {
            console.log(`${действие}: гоп!`);
    
            return Promise.resolve();
        }
    
        function делатьДолго(действие, сколько = null) {
            return new Promise(resolve => {
                const сколькоЖдать = сколько === null ? Math.floor(Math.random() * 1000) : сколько
                console.log(`${действие}: будет готово через ${сколькоЖдать}`)
    
                const идентификатор = setInterval(() => {
                    console.log(`${действие}: в процессе...`)
                }, 35)
    
                setTimeout(() => {
                    console.log(`${действие}: гоп гоп!!!`)
                    clearInterval(идентификатор);
                    resolve();
                }, сколькоЖдать)
            })
        }
    
    })({ яицНет: false, солиНет: true })
    
  30. webdev Это типа подписки на события?

    Нет. Подписка это обычный код, срабатывающий по событию. Аналог - обработчик прерывания. А это именно асинхронщина. Создаем диалог и "уходим", при закрытия автоматически вызывается callback назначенный обработчик.

    webdev Как ты подпишешься сразу на два события, типа "сковородка разогрета, и обнаружены яйца"?

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

    webdev Тут обещания удобно использовать.

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

    Ответы: (38) (39)
  31. Вот на промисах как удобно забабахал программу. Вообще же четко выглядит, и читается хорошо.

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

  33. Выглядит эффектно, но как это отлаживать?

    Ответы: (37)
  34. ТеньД Выглядит эффектно, но как это отлаживать?

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

    Ответы: (41)
  35. ТеньД Аналог - обработчик прерывания. А это именно асинхронщина.

    Не вижу разницы. И то, и то - вызов кода по событию. Асинхронщина в обоих случаях. Только название разное.

    Ответы: (41)
  36. ТеньД Обработчик один, но внутри можно понять какое событие выстрелило.

    А по условию задачи нужно чтобы обработчик вызвался только после выстреливания обоих событий. У меня это сделано с помощью Promise.all, с передачей массива событий в качестве параметра. Обработчик не будет выполняться когда это не нужно.

    Ответы: (41)
  37. Курильщик dart + flatter.
    typescript/js + react
    TypeScript + angular

    В реакте обработчик срабатывает при каждом изменении входных параметров. Как в Экселе.

  38. webdev В отладчике ставишь точку останова, и отлаживаешь, какие проблемы?

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

    webdev Но я там добавил вспомогательные функции, которые в консоль печатают весь ход выполнения программы.

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

    webdev Не вижу разницы. И то, и то - вызов кода по событию. Асинхронщина в обоих случаях. Только название разное.

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

    webdev А по условию задачи нужно чтобы обработчик вызвался только после выстреливания обоих событий.

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

    Ответы: (42)
  39. ТеньД Проблема в том, как найти где следует поставить точку останова?

    Там где ищешь проблему, там и ставишь точку останова. На каждый обработчик можно поставить. Там где then, все линейно. Они все выполняются по порядку:
    (разогретьСковороду И дождатьсяЯйца)
    разбитьЯйца
    ожиданиеСоли
    посолитьЯичницу
    жаритьЯичницу
    податьЯичницу

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

    Ответы: (43)
  40. webdev Там где ищешь проблему, там и ставишь точку останова.

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

    Ответы: (44)
  41. ТеньД Злоебучий 1с не дружит с многопоточной отладкой. Сидишь отлаживаешь код фонового задания или com сеанса, при очередном F8 оказываешься в абсолютно другом модуле. А тот код который отлаживался, "под шумок" уже проскочил. Просто ставить бряки на входе не помогает, надо прицельно обкладывать ими "больное" место.

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

или зарегистрируйтесь чтобы ответить!