Comments 53
И как всегда ни слова о реактивном программировании с вытеснящим полиморфизмом. Аудитория негодует!
Свитч с веткой default – это не конечный автомат вообще.
Мне кажется, что есть ещё третья ошибка, возникающая при программировании конечных автоматов, от которой спасают акторы. Я называю её "object reentrancy". Суть вот в чём: функция или метод, принимающая событие, должна сделать две вещи: поменять состояние, и выполнить всякие side effect-ы. Представим, что она это делает в таком порядке: сначала выполняет сайд-эффекты, а потом меняет состояние. Если side effect - это синхронный вызов, то в его процессе может породиться новое событие, и в том же потоке выполнения снова вызовется метод, принимающий событие. Но состояние-то осталось старым! Потом эта цепочка начнёт раскручиваться, и состояния начнут обновляться задом наперёд. Mutex-ы не спасут, они обычно позволяют себя повторно захватить потоку, который ими и так уже владеет.
В ООП есть понятие "class invariant". Это нормально, что внутри метода этот инвариант временно ломается, пока это не наблюдаемо извне. Но если метод временно передаёт управление, например в какой-нибудь callback, пока инвариант сломан, то возможна неожиданная жопа. Я с таким сталкивался, когда у меня несколько стейт-машин общались друг с другом.
Поэтому для конечных автоматов есть два варианта: или дисциплина "сначала меняем состояние, потом выполняем остальные действия", или все события только через очередь. Мне больше нравится первый вариант, очень люблю сквозную отладку.
сначала меняем состояние, потом выполняем остальные действия
3 способа есть
Просто синхронно вызвать (state → side-effect в том же стеке, lock для атомарности)
Outbox (state и «что отправить» коммитятся вместе)
Saga / In-Progress
state=InProgress; call remote; on OK → state=Done; on Fail → Rollback|Retry.
Нужна логика повтора/компенсации, зато нет общей БД.
«Сначала state, потом эффекты» это верно, но способы доставки/исполнения эффектов дают три варианта (стек, outbox, saga).
Сага — по сути та же стейтмашина, это бесконечная рекурсия.
логика повтора/компенсации
В конечном автомате? Вы там на тяжелых наркотиках, что ли?
А... Понял
В терминах Сага (императивно) это Retry/Rollback
В терминах конечного автомата (декларативно) это переход в состояние need retry / need rollback
А кто толкнет дальше из need состояния?
сервис подписчик
Какой еще сервис? Уже и сервис нарисовался? А хотели ведь мирно без гостей посидеть.
В акторной можно всё в одном
В ФП кто-то должен толкнуть
В ООП всё плохо когда его дёргают из нескольких потоков управления. Своего потока управления в ООП нет и начинается хаос. А в акторной модели свой поток управления за счёт очереди и может ещё чего то.
Да, просто за счет очереди и изолированного пространства. Инкапсуляция по-взрослому: не существует способа её нарушить.
Это реализуемо, конечно, и в ФП, и в ООП, но за счет дополнительных «сервисов» (что и ладно бы) — но вдобавок оно не страхует от ошибок разработчика, а вот это уже — труба для прикладного языка.
Намекаете, что конечный автомат декларативен до мозга костей и императивные действия не царское его дело?
сначала меняем состояние, потом выполняем остальные действия
А если «остальное действие» не выполнилось? Просто не смогло по независящим от нас причинам? Выбросило исключение?
Первый вариант некорректен, в процессе перехода могут возникнуть проблемы, что переход произойдёт не туда.
Реентрабельный КА не представляю, это ересь)
Что значит реентрабельный?
Стандартное определение
Стандартное определение — синоним потокобезопасности. У меня в тексте есть полноценный пример потокобезопасного конечного автомата, который можно запустить и попробовать поломать.
Вообще то потокобезопасность необходимое, но недостаточное свойство для реентерабельности. В Википедии, которую Вы за источник не уважаете, тем не менее это описано.
Похоже, у Вас больше склонность к теоретическим знаниям, что было отмечено комментаторами и в прошлой статье.
КА кстати является лучшим примером, моего 1го высказывания выше.
Больше, чем что? Вы не могли бы использовать конвенционный русский язык, чтобы не приходилось переспрашивать по два раза?
Не знаю, о каких комментаторах идёт речь. Или говорите прямо, или кокетничайте где-нибудь в другом месте.
Почитал википедию, мой пример демонстрирует полную реентрабельность и в викийном определении.
А вы говорите, зачем нужно ООП. Чтобы State снаружи нельзя было изменять, вот зачем. Я конечные автоматы тоже предпочитаю делать в отдельном потоке, а управление через методы типа bool Stop() {must_stop=true;}
, где must_stop - это приватный флаг и будет обработан там, где надо.
Варианты:
• Автомат живёт дольше процесса, влияет на деньги/внешний мир → фиксируем каждый event или хотя бы снапшот в durable storage.
• Автомат ограничен одним вызовом функции/потоком, можно легко пересчитать → держим state в ОЗУ (переменная, стек, closure). Необходимости /дополнительного удобства в ООП не возникает, но если это ООП язык, то его использование здесь естественно.
Граница проходит по требованиям к надёжности и времени жизни.
А... Понял
польза ООП перевешивает когда:
• состояние уходит за пределы одной функции (передаём, кешируем, шарим между потоками);
• нужно запретить нелегальные переходы (метод next() проверяет allowed-таблицу);
• автомат эволюционирует, его будут трогать другие разработчики.
А если нерадивый коллега вызовет Stop
в недозволенном месте?
Ну и да, чтобы запретить менять что-то снаружи — ООП не требуется.
Тут разница в том, что в одном случае нерадивый коллега дёргает стоп-кран, а в другом случае - вежливо просит машиниста поезда остановиться. А машинист поезда может его так же вежливо послать ждать следующей остановки.
Если честно, я не очень понимаю, как это должно работать. Можете маленький кусочек кода показать?
Он про инкапсуляцию, которая защищает от некорректных изменений мутабельного состояния
Это с каких пор инкапсуляция защищает от некорректных изменений мутабельного состояния?
Это одно из главных назначений инкапсуляции: защитить мутабельное состояние объекта от некорректных внешних изменений.
Может, не всегда получается, но ЭТО ДРУГОЕ, ВЫ НЕ ПОНИМАЕТЕ! (С) лол
В конкурентной среде для ООП все плохо. Несколько потоков пытаются переписать один объект и нет единой точки правды. В акторной модели актор сам - точка правды.
а... да... и еще
поток (Control Flow) для ООП является внешним, обьект в ООП пассивен и когда к нему обращаются из нескольких потоков управления то поучается хаос
актор имеет свой поток управления
я всегда воспринимал поток как OS thread а тут вон оно как)
А в каком языке вы чувствуете себя наиболее комфортно? Давайте я найду годную реализацию акторной модели для него, и тогда сможем предметно поговорить.
C#
но стараюсь быть полиглотом
Я в курсе, что есть Akka.net но не пробовал
стараюсь быть полиглотом
Это понятно, все нормальные разработчики стараются. Но обсуждать новое эффективнее в той области, где не возникает базовых вопросов.
У Akka.NET же крутейшая документация. Примеры, правда, так себе, наподобие «как нарисовать сову» — но вот этот, вроде, не переусложнен нерелевантными деталями.
Маленький не получится, для этого придётся какой-то игрушечный пример придумывать.
Ну вот например у меня есть объект для вывода звука одновременно на несколько устройств, и у него есть такие состояния, которые должны проходить последовательно:
STOPPED,
ENUMERATE_RUNTIME_MODULES,
INIT_DEVICES,
START_DEVICES,
WAIT_FOR_BUFFERS,
STOP_DEVICES,
FREE_DEVICES
Запросы на изменение состояния обрабатывается в начале каждого. И если запрос на остановку приходит при переходе с INIT_DEVICES на START_DEVICES - то состояния WAIT_FOR_BUFFERS и STOP_DEVICES можно пропустить и переходить сразу на FREE_DEVICES. А в состояниях STOPPED, STOP_DEVICES и FREE_DEVICES его отслеживать не имеет смысла.
Соответственно запрос на старт имеет смысл обрабатывать только в состоянии STOPPED, а объект в состоянии STOPPED висит на блокирующем событии, чтобы не расходовать зря процессорное время, поэтому об этом нужно дополнительно просигнализировать объекту межпоточной синхронизации, прямой доступ к которому давать снаружи тоже не самая лучшая идея.
а объект в состоянии STOPPED висит на блокирующем событии, чтобы не расходовать зря процессорное время, поэтому об этом нужно дополнительно просигнализировать объекту межпоточной синхронизации, прямой доступ к которому давать снаружи тоже не самая лучшая идея.
Это всё, что угодно, но не конечный автомат.
Ну назовите это как "асинхронный конечный автомат", суть от этого не меняется. Состояния явно прописаны, их количество конечно, в один момент времени существует только одно детерминированное состояние.
Этак вы любую программу назовёте конечным автоматом (кстати, встречал на Хабре и такое мнение).
Этак и вы любую программу назовёте не конечным автоматом.
Технически, любую программу можно описать конечным автоматом.
Нет, в силу неразрешимости проблемы останова.
А кто сказал, что переходы конечного автомата должны выполняться за фиксированное время?
Это теософская дискуссия. Идрис умеет доказывать тотальность функций, поэтому я критичные места реализую сначала на нём.
Это поле «state», про которое я в самом первом предложении написал, что воюю с непониманием, чем оно отличается от конечного автомата.
Спасибо! Надеюсь, что вас забанили ненадолго.
такое чувство что общество стало очень чувствительным)
Гарантийное обслуживание конечных автоматов