Автоматы и сознание
Конечные автоматы - язык состояний
Цели урока
- Понимать что FSM = состояния + входы + переходы, и где это применяется
- Знать формальное определение DFA (Q, Σ, δ, q₀, F) и эквивалентность NFA/DFA
- Видеть связь регулярных выражений с FSM по теореме Клини
- Распознавать опасные паттерны regex (ReDoS) и понимать как защититься
Предварительные знания
- Базовое понимание множеств и функций
- Базовый опыт программирования (любой язык)
- Желательно: интуиция про регулярные выражения
TCP-соединение - это FSM. Регулярки - это FSM. NPC в Doom - это FSM. Лифт - это FSM. Один концепт 1943 года (Маккаллок-Питтс) до сих пор лежит в основе цифрового мира.
- **TCP/IP стек (RFC 793, 1981)** - 11 состояний на каждое соединение, диаграмма не менялась 44 года
- **Cloudflare ReDoS (2019)** - один regex с вложенным квантификатором положил 1.1.1.1 на 27 минут
- **Doom (1993)** - все враги работают через FSM с состояниями patrol/chase/attack/flee
- **Stack Overflow (2016)** - 34 минуты простоя из-за regex с экспоненциальным backtracking
- **React state machines (XState)** - современная библиотека для UI-компонентов на основе FSM
Маккаллок и Питтс - первая модель искусственного нейрона
В 1943 году нейрофизиолог Маккаллок и логик Питтс опубликовали "A Logical Calculus of Ideas Immanent in Nervous Activity" - первую формальную модель нейрона как логического элемента. Из этой статьи выросли две ветки: 1. формальные грамматики и теория автоматов через Клини и Хомского 2. искусственные нейронные сети через Розенблатта (perceptron 1958) и Хинтона (deep learning 2006). Один документ - корень и FSM, и современного ИИ.
Что такое конечный автомат
**Каждый раз когда открывается страница сайта, происходит TCP handshake. И каждое TCP-соединение - это конечный автомат с 11 состояниями: CLOSED → LISTEN → SYN_SENT → SYN_RECEIVED → ESTABLISHED → FIN_WAIT_1 → ... В RFC 793 (1981) этот автомат прорисован как диаграмма - и за 44 года ничего не изменилось.** Конечные автоматы - не академическая абстракция. Это базовый язык, на котором написана инфраструктура цифрового мира.
**Конечный автомат (Finite State Machine, FSM)** - модель поведения с 4 компонентами: 1. конечное число **состояний** 2. **алфавит входов** (что может прийти) 3. **функция переходов** (на какой вход - в какое состояние) 4. **начальное состояние**. Никакой памяти кроме текущего состояния.
Где работают FSM прямо сейчас
| Где | Состояния | Масштаб |
|---|---|---|
| TCP/IP стек | 11 состояний на соединение | ~10^10 активных соединений в мире |
| Регулярные выражения (NFA/DFA) | Зависит от паттерна | Парсинг трафика, валидация форм - миллиарды раз в секунду |
| Лексеры компиляторов | Состояния для чисел/строк/идентификаторов | Каждая компиляция любой программы |
| NPC в играх (Doom 1993, GTA V) | Patrol → Chase → Attack → Flee | Миллионы игровых сессий |
| Vending machines, банкоматы, лифты | Idle → Selecting → Dispensing | Физический мир |
| UI-компоненты (React state) | Loading → Success → Error | Каждое веб-приложение |
Простой пример - турникет в метро
**2 состояния, 2 входа (`card`, `push`), 4 перехода.** Турникет не знает сколько раз через него прошли, кто прошёл, в какое время. Только текущее состояние и реакция на следующий вход. Это и есть суть FSM - **полное поведение системы определяется текущим состоянием**, история не нужна.
FSM - это просто куча `if/else` в коде
FSM - это явная модель, где состояния и переходы видны и формально проверяемы
Куча `if/else` обычно тянет за собой неявное состояние через десятки флагов и переменных - тестировать сложно, ошибок легион. FSM делает все возможные состояния явными. Можно нарисовать диаграмму, доказать корректность, найти недостижимые состояния. TLA+ и SPIN автоматически проверяют такие модели на свойства типа "турникет никогда не пропустит без оплаты".
Что отличает FSM от обычной программы с переменными?
Формальное определение и DFA vs NFA
**Формально DFA (Deterministic Finite Automaton) - это пятёрка** `M = (Q, Σ, δ, q₀, F)`:
- **Q** - конечное множество состояний (например `{LOCKED, UNLOCKED}`)
- **Σ** - входной алфавит (`{card, push}`)
- **δ: Q × Σ → Q** - функция переходов (даёт новое состояние по текущему + входу)
- **q₀ ∈ Q** - начальное состояние (`LOCKED`)
- **F ⊆ Q** - множество принимающих состояний (для распознавателей строк)
**DFA (детерминированный)**: на каждый вход в каждом состоянии - ровно один переход. **NFA (недетерминированный)**: на один вход может быть 0, 1 или несколько переходов одновременно. NFA удобнее проектировать, DFA быстрее исполнять. Знаменитая теорема Рабина-Скотта (1959) доказывает что любой NFA можно превратить в эквивалентный DFA - правда с экспоненциальным взрывом числа состояний в худшем случае.
Пример: распознать строки заканчивающиеся на 'ab'
**Цена детерминизма:** при превращении NFA → DFA число состояний может вырасти как 2^n. Поэтому реальные движки регулярных выражений (Go regexp, RE2 от Google) используют NFA напрямую с симуляцией всех возможных состояний параллельно - O(n×m) гарантировано вместо экспоненциального взрыва PCRE.
NFA мощнее DFA - может распознать больше языков
NFA и DFA эквивалентны по выразительной силе - распознают точно один и тот же класс языков (регулярные)
Это знаменитый результат Рабина-Скотта 1959 года, за который они получили Turing Award в 1976. NFA удобнее писать и часто компактнее, но любой NFA конвертируется в DFA (subset construction). Истинное расширение силы даёт только добавление памяти - стек (PDA = pushdown automaton) или ленту (Turing machine).
DFA с 10 состояниями превращён в эквивалентный NFA. Сколько состояний может иметь NFA в наилучшем случае?
Регулярные выражения - это FSM
**Когда пишется `/^[a-z]+@[a-z]+\.[a-z]+$/` - под капотом строится FSM.** Каждый символ в регэксе превращается в состояния и переходы по теореме Клини (1956). Это самое массовое практическое применение FSM в индустрии: каждая валидация формы, каждый grep, каждый парсер логов.
Соответствие операторов и FSM-конструкций
| Regex | FSM-конструкция | Пример |
|---|---|---|
| `a` | Один переход по символу 'a' | q0 --a--> q1 |
| `a|b` | Альтернатива - два пути из одного состояния | Из q0 либо a, либо b |
| `a*` | Цикл с возможностью пропустить | q0 --a--> q0 (петля) |
| `ab` | Конкатенация - последовательные переходы | q0 --a--> q1 --b--> q2 |
| `a+` | Один или больше = a · a* | Минимум один переход + петля |
| `[a-z]` | Один переход с большим алфавитом | 26 параллельных переходов |
Знаменитый случай ReDoS
**Cloudflare, 2 июля 2019. 13:42 UTC. Один новый regex в WAF-правиле кладёт CPU всех серверов до 100% за 10 минут. Сайт 1.1.1.1 (DNS) недоступен 27 минут.** Причина - паттерн `(?:(?:"|'|\]|\}|\\|\d|...|\?)+)+` создал NFA, в котором один вход порождает экспоненциальное число одновременных путей. Это ReDoS - Regular Expression Denial of Service.
**Защита от ReDoS:** 1. использовать движки на NFA-симуляции - RE2 (Google), Rust regex, Go regexp. 2. запрещать вложенные квантификаторы в user-input regex. 3. ставить timeout на исполнение паттерна. Stack Overflow в 2016 лежал 34 минуты из-за такого regex - публичный postmortem стоит прочесть.
Главное из урока
- FSM = состояния + входы + переходы. Полное поведение определяется текущим состоянием
- FSM везде: TCP, regex, lexers, NPC, UI - один и тот же аппарат
- DFA (один переход на вход) и NFA (несколько) эквивалентны по выразительности (Рабин-Скотт 1959)
- Регулярные выражения = NFA по теореме Клини (1956). Все движки строят FSM под капотом
- Без памяти FSM не может считать. Чтобы посчитать `aⁿbⁿ` нужен PDA (стек), а для произвольных языков - Turing machine
Дальше в курсе
От простых FSM к моделям с памятью и вероятностью.
- Цепи Маркова — FSM с вероятностями переходов - модели текста, рекомендации, PageRank
- Скрытые марковские модели (HMM)
Ключевые понятия
- FSM - это пятёрка (Q, Σ, δ, q₀, F): состояния, алфавит, функция переходов, начальное состояние, принимающие состояния
- Полное поведение FSM определяется только текущим состоянием - история не нужна
- DFA: на каждый вход - ровно один переход. NFA: на один вход возможны несколько переходов одновременно
- DFA и NFA эквивалентны по выразительной силе (теорема Рабина-Скотта, 1959) - распознают один и тот же класс регулярных языков
- Регулярные выражения = FSM по теореме Клини (1956): каждый паттерн компилируется в граф состояний
- ReDoS - атака на движки с backtracking: вложенные квантификаторы создают экспоненциальное число путей. Защита - RE2, Go regexp (NFA-симуляция, O(n))
Вопросы для размышления
- Подумай о приложении которым пользуешься ежедневно. Какие state machines в нём явно работают (loading-states, формы, навигация), а какие неявно (auth-flow, online/offline переходы)?
Связанные уроки
- fl-01-intro — Регулярные языки - это ровно то, что распознают конечные автоматы
- aut-02-markov-chains — Цепи Маркова - вероятностное расширение FSM с переходами по вероятностям
- nlp-01 — Токенизаторы (tiktoken) - это DFA над Unicode; FSM в основе обработки текста
- comp-01 — Машина Тьюринга - расширение конечного автомата с бесконечной лентой памяти
- fl-06-dfa