Насчёт RESTfull я с вами согласен — это просто абстрактный термин, которым принято обозначать архитектуру приложений, якобы сделанную по принципам REST. Особой конкретики в этом нет, т.к. следуя принципам REST можно сделать разные архитектуры. По моему опыту 95% разработчиков, к сожалению, вообще не понимают REST, но при этом пытаются делать приложения и писать статьи про всё это. В результате интернет заполнен кодом и текстами не соответствующими принципам REST. И это только ещё больше запутывает, особенно новичков.
Но тема null-ов и отсутствующих полей не имеет отношения к REST как таковому. Просто есть 3 разных состояний поля в сущности: поле есть и имеет значение, оно null или его вообще нет. И эти состояния можно использовать в разных целях, в зависимости от контекста. Какие из них использовать и где — обычно понять не сложно, просто не забывая, что это действительно 3 разных состояния, которые лучше не смешивать. А потом задаться вопросами: какие из этих состояний допустимы, что каждое их них означает в конкретном случае. И если получается, что два разных состояния означают одно и то-же, то это повод хорошо подумать, действительно ли именно это вы хотели.
Про restfull вы несколько заблуждаетесь. Нет такого "правила" что возвращать надо то же что получил. Можно вообще получать в json, а отдавать в xml с другим набором полей. И в rest передают в обе стороны не сущности, а их некую репрезентацию. Какая при этом будет репрезентация — дело ваше, лишь бы её было достаточно для решения задач.
Но естественно часто просто удобнее если набор входных параметров является подмножеством результирующих свойств созданной сущности.
Возвращать только то что изменилось — это не удобно, нарушает схему ответа (разработчики клиентов вам точно спасибо не скажут), и накладно — если сущность была изменена другим клиентом, то придётся запрашивать её заново полностью, т.к. ваш запрос на изменение вернёт только то что он изменил.
Тут обязательно надо разделять "схему" входных параметров, и "схему" того что возвращает API (о чём говорится в статье). Входные параметры могут иметь более свободную форму. Например можно сделать так, что отсутствие в запросе поля или если оно равно null, будет преобразованно десериализатором-валидатором в "значение по умолчанию" (пустой массив).
А вот в схеме возвращаемого результата лучше избегать вольностей. Если поле должно быть всегда массивом — всегда возвращать массив, даже если по каким то причинам этого массива нет в хранилище данных.
Из моего опыта null нужен обычно только в тех случаях, когда он уместен в конкретной ситуации и не может быть иначе выражен конкретным типом данных. Например у типа datetime нет ни какого подходящего значения, которое бы можно было во всех ситуациях использовать в качестве "нет значения". Тут только null подходит. Для целых чисел иногда можно использовать 0, если бизнес-логика не допускает использование нуля в качестве нормального значения. С пустыми строками аналогично.
В случае коментов пустой массив это нормальная абстракция для "ещё нет коментов". Хотя бы потому что коменты надо куда-то добавлять. В null их не добавишь, а потому это не очень подходящее значение. Только если не использовать его как признак того, что коменты запрещены и их нельзя добавлять вообще.
Я лично стараюсь избегать использовать null без необходимости, т.к. он добавляет проверок в программе.
Что и как надо посылать при создании сущности — зависит от того нужено этоили нет. Если null допустимое значение для поля, то почему бы его не послать в POST запросе. Но можно так же сделать null как дефолтное значение поля при создании сущности. И тогда это поле можно вообще не посылать.
Не надо пытаться экономить на спичках и интерпретировать одно через другое.
Пустая строка — это всё ещё строка, а не null и тем более не отсутствие поля. Тоже самое и с пустым массивом.
Null не тоже самое, что отсутствие поля. Например, в запросе на обновление сущности, передав в поле expires_time значение null мы сообщим что ресурс больше не имеет срока жизни. А если вообще не передать поле — значит его не надо менять, а оставить текущее значение.
А нельзя что-ли сначала к МКС потихоньку подключать новые модули с одновременным выводом из эксплуатации и "сбросом" в океан старых модулей, пока вся станция не обновится? Обязательно надо сначала всё старое разломать, и потом с нуля строить?
"Косяк" с заимствованием вместе с "арабскими цифрами" ещё и арабскую запись чисел справа-налево вроде понятен. И я даже могу представить себе как бы это могло выглядеть и использовалось бы при "правильной" записи слева-направо. Но не хватает фантазии представить как бы это звучало в устной речи (и записи чисел словами). По моему в этом контексте как раз более важным является порядок числа, который в текущем виде озвучивается первым (две тысячи четыреста одиннадцать). Точно так же как он и записывается цифрами (2411). Так что может всё дело не в арабском справа-налево, а в том как было удобнее числа озвучивать устно?
Как уже отметили выше, телевизор — это не тот прибор, который хочется часто менять. С одной стороны хороший ТВ не дешёвый, а с другой — его замена редко приведёт к длительному "вау-эффекту". За последние 10 лет пожалуй единственным серьёзным улучшением в телевизорах было внедрение 4к разрешения (хотя количество контента для него всё ещё не назавёш достаточным).
В перспективе 10-летнего срока использования телевизора, совсем на задний план уходит его смарт-начинка. Она уже через пару лет станет бесполезной и устаревшей. Поэтому для меня основным критерием выбора ТВ в текущий момент, являются исключительно его основные "железные" характеристики: матрица, разрешение, диагональ, набор входных разъёмов и др. А программную обвязку я обеспечу внешней приставкой, которая дешевле и её можно чаще менять, получая больше впечатлений от этого мероприятия.
Если клиент где-то уже хранит у себя "сессию" (хотя бы авторизационный токен), то точно там же можно хранить и выбранный юзером язык. А пока юзер не авторизовался, то очевидно клиент даже из бекенда не получит эти данные. В качестве дефолта, можно временно использовать настройки браузера, пока пользователь явно не поменяет его.
Если ваш кеш работает на уровне HTTP, то можно взять готовые решения (например Varnish) которые умеют в wildcard. Или заменить memcache на Redis, он умеет в wildcard.
Лучше и проще доверять клиенту. А настройку в бекенде использовать для отправки пользователю всяких рассылок и уведомлений, когда нет информации о языке клиента, который будет просматривать эти сообщения. Это позволит пользователям настраивать на разных клиентах разные языки независимо от настроек бекенда. Так же можно использовать настройку из бекенда в качестве дефолта, если вам вдруг почему то не нравится использовать настройки браузера в качестве таковых. Получать эту настройку можно в момент авториазции юзера. А до тех пор использовать настройку браузера или куки/localStorage, в которых можно сохранять язык с последней "сессии".
Почему бы не сделать так, что бы "язык пользователя" передавался клиентом в запросе (например через HTTP заголовок Accept-Language)? Его можно использовать для формирования ключа к кешу. Тогда не пришлось бы заморачиваться с очисткой кеша по сообщению из Кафки и не было бы задержки.
Это ведь один из архитектурных принципов в диссертации Филдинга для создания масштабируемых распределённых приложений. Данные, необходимые для выполнения запроса, должны приходить от клиента. Как только вы пытаетесь брать эти данные ещё от куда-то — это становится бутылочным горлышком и мешает горизонтально масштабировать приложение.
Если "второй" клиент, после INCR получил > 1 и сразу же упал, или просто забил на выполнение DECR, то будет "больно" пока не кончится TTL.
А может быть ещё интереснее — второй клиент сделает свой DECR после того как первый успеет сделать свой. В итоге получим в ключе -1.
Посмотрел исходники.
Вы никогда не обращали внимание на то, что в других, "взрослых" фреймворках HTTP-заголовки представлены не совсем обычным питонячим словарём? Обычно это нечто называемое, как минимум, MultiDict (а в идеале там ещё и case-insensetive сравнение ключей).
И ведь не просто так ASGI передаёт приложению заголовки в виде списка тюплов. А вы довольно смело берёте и превращаете этот список в самый обычный словарик.
Думается мне, что вас ждёт ещё много открытий на вашем пути. И с каждым новым открытием ваш фреймворк скорее всего будет становиться медленнее и медленнее.
Скорее всего ваш выбор основывается на ваших текущих ощущениях. Но вы слишком переоцениваете знания и навыки новичков в программировании. Вам может и кажется всё элементарно в изучении C. Примерно так же как водитель со стажем может "на автопилоте" ехать домой и параллельно болтать с пассажиром, и делать ещё что-то. А вот новичкам все вещи, о которых вы даже уже не задумываетесь, будут камнем преткновения и тормозом в дальнейшем продвижении.
Да, можно сказать в два предложения зачем нужен #include и что-такое void. Но скорее всего это не добавит ученикам понимания, потому как они ещё не могут понять саму суть проблем, для решения которых нужны эти штуки. И потому это вызовет только больше вопросов (у тех кому интересно, остальные просто забьют):
Зачем подключать библиотеку стандартных функций (и что это вообще такое)? Я же просто хочу вывести текст на экран. Это ведь элементарно.
Пустой тип...?! Что?! А есть в С "мутный тип"?
Эмм… символ перевода строки? Это что-ли "буква" такая? Что за код 10? Нельзя что ли просто нажать "Enter" как в Ворде?
А указатели, ссылки и выделение памяти — это действительно не тема новичков (даже на 10-ый урок). Им бы освоить базовые конструкции (циклы, условия, переменные и функции), и научится ими пользоваться там где надо.
Давно я увидел довольно интересный аргумент, почему C не должен быть первым языком программирования в школах. В школе не у всех есть мотивация преодолевать доп. барьеры, которые выставляет язык при его изучении. Но в целом идею можно распространить не только на школьное образование.
Посмотрите на hello-world написаный на С:
И представьте как преподаватель (или книга для новичков) будет объяснять этот код:
#include <stdio.h> — не смотрите пока на это. Оно просто нужно, но зачем — будет рассказано через 5-6 уроков.
void main() — это функция main. С неё начинает выполняться программа. Что такое функция вы узнаете на 3-ем уроке. И не смотрите пока на void — расскажу потом про то что это такое.
{ и } — это начало и конец функции и других блоков.
printf("Hello world\n"); — это строка выводит на экран указанный текст. Не обращайте внимания на \n — позднее вы узнаете, что это такое и зачем нужно.
В итоге большая часть первого кода, с которым знакомится новичок — это набор каких-то непонятных инструкций и терминов, на которые ему рекомендуют пока не обращать внимания, т.к. сложно объяснить на первом уроке их смысл и назначение без того что бы не углубиться в другие темы.
Как я понял, у вас автоматы напрямую взаимодействуют с неким внешним "планировщиком". Как минимум добавляя в него новые автоматы, результат которых им нужен.
В случае с корутинами предполагается, что сами они не работают напрямую с евент-лупом, а просто возвращают некий future-like объект (или promise). И евент-луп не вернёт управление исходной корутине пока не будет завершено выполнение этой future (а если точнее, то возврат управления — это просто callback прописанный внутри future). Future — это по сути "состояние автомата", которое имеет как минимум три значения: выполняется, завершено, ошибка. Переход из одного состояния в другой выполняется путём "внешнего воздействия" на future. И в него можно добавить callback-и на завершение и ошибку. По моему это примерно тоже самое, что у вас делается с помощью pFAwaitSleep->FCall().
И мне почему-то кажется, что future — это результат, который получился из попыток унифицировать и упростить работу с автоматами в том виде как это реализовано у вас. Евент-луп работает с future-объектами, а что там за ними стоит, какие калбяки они вызывают — это не его забота.
Фактически вся внутрянка, на которой работает асинхронный код — это всё автоматы. А за счёт специальных языковых возможностей (async/await), у программиста есть возможность писать короткий, легко читаемый код в "синхронном" стиле. В принципе можно писать код без async/await, и явно оперировать с Future-ами. Но это не удобно и порождает сильно вложенный код и callback-hell.
Согласен, что возможность отследить состояние автомата в явном виде — это удобно в некоторых случаях. При желании можно и в корутинах отслеживать статус, но он не будет обязательным для работы самой корутины, а просто как некая мета-дата. Но обычно в этом случае используют "примитивы синхронизации" (которые, наверное, тоже автоматы). Они позволяют синхронизировать разные "задачи" без использования "магических констант" и нарушения инкапсуляции, когда код снаружи "автомата" почему-то знает о его внутренних статусах.
PS: На самом деле меня не коробят ваши автоматы в C++. Пишите на нём как вам удобно. Больше всего меня задело, то что вы не зная Python пишите на нём совершенно некорректный код. А потом ещё и делаете какие-то выводы на его основе, и даже местами сравниваете с C++. Не надо так. Пишите на C++, и мне будет всё равно. Я наверное даже читать не буду, т.к. я давно на нём не пишу и не планирую в будщем.
Хорошо, когда всё "обычно" и не выходит за рамки того "как правило". Но в проектах, над которыми работают несколько человек, которые при этом периодически меняются, лучше на это не надеяться. Кто-нибудь обязательно запилит автомат, который упрётся в лимит "32", и засунет в него всю бизнес-логику приложения. И будет он занимать 1000+ строк.
Хотя это конечно ваше дело, видимо вы или совсем не работали в команде. Или делали небольшие приложения, которые вы лично и поддерживали, и вам не приходилось каждые полгода объяснять новым членам команды, что означают все эти иксы и игреки. И в каком именно игреке надо искать код, который нужно поправить для решения задачи. Можно даже найти плюс в таком подходе, не надо ломать голову над одной из сложнейших задач программирования — придумывание названий.
Для меня же ваш код выглядит как результат работы обфуксатора или минимайзера. Там примерно такие же названия функций из 1-2 символов.
Говоря про "абстрактный" автомат, я типа намекнул, что неплохо бы добавить конкретики. Какого именно генератора? Что он должен делать? А не просто "граф генератора", как будто у любого генератора и автомата есть граф по умолчанию.
Я могу показать вам код sleep в виде генератора. Думается мне что граф у него точно такой же как у вашего PSleep, зато читается и понимается в разы проще, и кода у него сильно меньше:
import time
def sleep(secs):
end = time.time() + secs
while time.time() < end:
yield
# Имитация примитивного евент-лупа (без евентов)
queue = [sleep(10), sleep(5)]
while queue:
iterator = queue.pop(0)
try:
# То же самое, что вызов метода .loop() у ваших автоматов
next(iterator)
# Возвращаем "корутину" в конец очереди
sleeps.append(iterator)
except StopIteration:
# "Автомат" завершил свою работу, не возвращаем его в очередь
pass
А вот так он будет выглядеть если его сделать без "сахара":
Согласитесь — это практически тоже самое, что ваш PSleep. Можете даже вынести проверку условия в отдельный метод и назвать его x1(), если вы пишете код лично для себя.
Насчёт RESTfull я с вами согласен — это просто абстрактный термин, которым принято обозначать архитектуру приложений, якобы сделанную по принципам REST. Особой конкретики в этом нет, т.к. следуя принципам REST можно сделать разные архитектуры. По моему опыту 95% разработчиков, к сожалению, вообще не понимают REST, но при этом пытаются делать приложения и писать статьи про всё это. В результате интернет заполнен кодом и текстами не соответствующими принципам REST. И это только ещё больше запутывает, особенно новичков.
Но тема null-ов и отсутствующих полей не имеет отношения к REST как таковому. Просто есть 3 разных состояний поля в сущности: поле есть и имеет значение, оно null или его вообще нет. И эти состояния можно использовать в разных целях, в зависимости от контекста. Какие из них использовать и где — обычно понять не сложно, просто не забывая, что это действительно 3 разных состояния, которые лучше не смешивать. А потом задаться вопросами: какие из этих состояний допустимы, что каждое их них означает в конкретном случае. И если получается, что два разных состояния означают одно и то-же, то это повод хорошо подумать, действительно ли именно это вы хотели.
Про restfull вы несколько заблуждаетесь. Нет такого "правила" что возвращать надо то же что получил. Можно вообще получать в json, а отдавать в xml с другим набором полей. И в rest передают в обе стороны не сущности, а их некую репрезентацию. Какая при этом будет репрезентация — дело ваше, лишь бы её было достаточно для решения задач.
Но естественно часто просто удобнее если набор входных параметров является подмножеством результирующих свойств созданной сущности.
Возвращать только то что изменилось — это не удобно, нарушает схему ответа (разработчики клиентов вам точно спасибо не скажут), и накладно — если сущность была изменена другим клиентом, то придётся запрашивать её заново полностью, т.к. ваш запрос на изменение вернёт только то что он изменил.
Тут обязательно надо разделять "схему" входных параметров, и "схему" того что возвращает API (о чём говорится в статье). Входные параметры могут иметь более свободную форму. Например можно сделать так, что отсутствие в запросе поля или если оно равно null, будет преобразованно десериализатором-валидатором в "значение по умолчанию" (пустой массив).
А вот в схеме возвращаемого результата лучше избегать вольностей. Если поле должно быть всегда массивом — всегда возвращать массив, даже если по каким то причинам этого массива нет в хранилище данных.
Из моего опыта null нужен обычно только в тех случаях, когда он уместен в конкретной ситуации и не может быть иначе выражен конкретным типом данных. Например у типа datetime нет ни какого подходящего значения, которое бы можно было во всех ситуациях использовать в качестве "нет значения". Тут только null подходит. Для целых чисел иногда можно использовать 0, если бизнес-логика не допускает использование нуля в качестве нормального значения. С пустыми строками аналогично.
В случае коментов пустой массив это нормальная абстракция для "ещё нет коментов". Хотя бы потому что коменты надо куда-то добавлять. В null их не добавишь, а потому это не очень подходящее значение. Только если не использовать его как признак того, что коменты запрещены и их нельзя добавлять вообще.
Я лично стараюсь избегать использовать null без необходимости, т.к. он добавляет проверок в программе.
Что и как надо посылать при создании сущности — зависит от того нужено этоили нет. Если null допустимое значение для поля, то почему бы его не послать в POST запросе. Но можно так же сделать null как дефолтное значение поля при создании сущности. И тогда это поле можно вообще не посылать.
Это совершенно 3 разные ситуации:
Не надо пытаться экономить на спичках и интерпретировать одно через другое.
Пустая строка — это всё ещё строка, а не null и тем более не отсутствие поля. Тоже самое и с пустым массивом.
Null не тоже самое, что отсутствие поля. Например, в запросе на обновление сущности, передав в поле expires_time значение null мы сообщим что ресурс больше не имеет срока жизни. А если вообще не передать поле — значит его не надо менять, а оставить текущее значение.
А нельзя что-ли сначала к МКС потихоньку подключать новые модули с одновременным выводом из эксплуатации и "сбросом" в океан старых модулей, пока вся станция не обновится? Обязательно надо сначала всё старое разломать, и потом с нуля строить?
"Косяк" с заимствованием вместе с "арабскими цифрами" ещё и арабскую запись чисел справа-налево вроде понятен. И я даже могу представить себе как бы это могло выглядеть и использовалось бы при "правильной" записи слева-направо. Но не хватает фантазии представить как бы это звучало в устной речи (и записи чисел словами). По моему в этом контексте как раз более важным является порядок числа, который в текущем виде озвучивается первым (две тысячи четыреста одиннадцать). Точно так же как он и записывается цифрами (2411). Так что может всё дело не в арабском справа-налево, а в том как было удобнее числа озвучивать устно?
Как уже отметили выше, телевизор — это не тот прибор, который хочется часто менять. С одной стороны хороший ТВ не дешёвый, а с другой — его замена редко приведёт к длительному "вау-эффекту". За последние 10 лет пожалуй единственным серьёзным улучшением в телевизорах было внедрение 4к разрешения (хотя количество контента для него всё ещё не назавёш достаточным).
В перспективе 10-летнего срока использования телевизора, совсем на задний план уходит его смарт-начинка. Она уже через пару лет станет бесполезной и устаревшей. Поэтому для меня основным критерием выбора ТВ в текущий момент, являются исключительно его основные "железные" характеристики: матрица, разрешение, диагональ, набор входных разъёмов и др. А программную обвязку я обеспечу внешней приставкой, которая дешевле и её можно чаще менять, получая больше впечатлений от этого мероприятия.
Почему бы не сделать так, что бы "язык пользователя" передавался клиентом в запросе (например через HTTP заголовок Accept-Language)? Его можно использовать для формирования ключа к кешу. Тогда не пришлось бы заморачиваться с очисткой кеша по сообщению из Кафки и не было бы задержки.
Это ведь один из архитектурных принципов в диссертации Филдинга для создания масштабируемых распределённых приложений. Данные, необходимые для выполнения запроса, должны приходить от клиента. Как только вы пытаетесь брать эти данные ещё от куда-то — это становится бутылочным горлышком и мешает горизонтально масштабировать приложение.
Если "второй" клиент, после INCR получил > 1 и сразу же упал, или просто забил на выполнение DECR, то будет "больно" пока не кончится TTL.
А может быть ещё интереснее — второй клиент сделает свой DECR после того как первый успеет сделать свой. В итоге получим в ключе -1.
Если что, то это не ASGI говорит, это спека на HTTP говорит, что может быть несколько заголовков с одинаковым именем. И это нормальная ситуация.
Посмотрел исходники.
Вы никогда не обращали внимание на то, что в других, "взрослых" фреймворках HTTP-заголовки представлены не совсем обычным питонячим словарём? Обычно это нечто называемое, как минимум, MultiDict (а в идеале там ещё и case-insensetive сравнение ключей).
И ведь не просто так ASGI передаёт приложению заголовки в виде списка тюплов. А вы довольно смело берёте и превращаете этот список в самый обычный словарик.
Думается мне, что вас ждёт ещё много открытий на вашем пути. И с каждым новым открытием ваш фреймворк скорее всего будет становиться медленнее и медленнее.
Скорее всего ваш выбор основывается на ваших текущих ощущениях. Но вы слишком переоцениваете знания и навыки новичков в программировании. Вам может и кажется всё элементарно в изучении C. Примерно так же как водитель со стажем может "на автопилоте" ехать домой и параллельно болтать с пассажиром, и делать ещё что-то. А вот новичкам все вещи, о которых вы даже уже не задумываетесь, будут камнем преткновения и тормозом в дальнейшем продвижении.
Да, можно сказать в два предложения зачем нужен
#include
и что-такоеvoid
. Но скорее всего это не добавит ученикам понимания, потому как они ещё не могут понять саму суть проблем, для решения которых нужны эти штуки. И потому это вызовет только больше вопросов (у тех кому интересно, остальные просто забьют):А указатели, ссылки и выделение памяти — это действительно не тема новичков (даже на 10-ый урок). Им бы освоить базовые конструкции (циклы, условия, переменные и функции), и научится ими пользоваться там где надо.
Давно я увидел довольно интересный аргумент, почему C не должен быть первым языком программирования в школах. В школе не у всех есть мотивация преодолевать доп. барьеры, которые выставляет язык при его изучении. Но в целом идею можно распространить не только на школьное образование.
Посмотрите на hello-world написаный на С:
И представьте как преподаватель (или книга для новичков) будет объяснять этот код:
#include <stdio.h>
— не смотрите пока на это. Оно просто нужно, но зачем — будет рассказано через 5-6 уроков.void main()
— это функция main. С неё начинает выполняться программа. Что такое функция вы узнаете на 3-ем уроке. И не смотрите пока наvoid
— расскажу потом про то что это такое.{
и}
— это начало и конец функции и других блоков.printf("Hello world\n");
— это строка выводит на экран указанный текст. Не обращайте внимания на\n
— позднее вы узнаете, что это такое и зачем нужно.В итоге большая часть первого кода, с которым знакомится новичок — это набор каких-то непонятных инструкций и терминов, на которые ему рекомендуют пока не обращать внимания, т.к. сложно объяснить на первом уроке их смысл и назначение без того что бы не углубиться в другие темы.
Как я понял, у вас автоматы напрямую взаимодействуют с неким внешним "планировщиком". Как минимум добавляя в него новые автоматы, результат которых им нужен.
В случае с корутинами предполагается, что сами они не работают напрямую с евент-лупом, а просто возвращают некий future-like объект (или promise). И евент-луп не вернёт управление исходной корутине пока не будет завершено выполнение этой future (а если точнее, то возврат управления — это просто callback прописанный внутри future). Future — это по сути "состояние автомата", которое имеет как минимум три значения: выполняется, завершено, ошибка. Переход из одного состояния в другой выполняется путём "внешнего воздействия" на future. И в него можно добавить callback-и на завершение и ошибку. По моему это примерно тоже самое, что у вас делается с помощью
pFAwaitSleep->FCall()
.И мне почему-то кажется, что future — это результат, который получился из попыток унифицировать и упростить работу с автоматами в том виде как это реализовано у вас. Евент-луп работает с future-объектами, а что там за ними стоит, какие калбяки они вызывают — это не его забота.
Фактически вся внутрянка, на которой работает асинхронный код — это всё автоматы. А за счёт специальных языковых возможностей (async/await), у программиста есть возможность писать короткий, легко читаемый код в "синхронном" стиле. В принципе можно писать код без async/await, и явно оперировать с Future-ами. Но это не удобно и порождает сильно вложенный код и callback-hell.
Согласен, что возможность отследить состояние автомата в явном виде — это удобно в некоторых случаях. При желании можно и в корутинах отслеживать статус, но он не будет обязательным для работы самой корутины, а просто как некая мета-дата. Но обычно в этом случае используют "примитивы синхронизации" (которые, наверное, тоже автоматы). Они позволяют синхронизировать разные "задачи" без использования "магических констант" и нарушения инкапсуляции, когда код снаружи "автомата" почему-то знает о его внутренних статусах.
PS: На самом деле меня не коробят ваши автоматы в C++. Пишите на нём как вам удобно. Больше всего меня задело, то что вы не зная Python пишите на нём совершенно некорректный код. А потом ещё и делаете какие-то выводы на его основе, и даже местами сравниваете с C++. Не надо так. Пишите на C++, и мне будет всё равно. Я наверное даже читать не буду, т.к. я давно на нём не пишу и не планирую в будщем.
Опечатка в коде. Вместо
sleeps.append(iterator)
надо
queue.append(iterator)
Хорошо, когда всё "обычно" и не выходит за рамки того "как правило". Но в проектах, над которыми работают несколько человек, которые при этом периодически меняются, лучше на это не надеяться. Кто-нибудь обязательно запилит автомат, который упрётся в лимит "32", и засунет в него всю бизнес-логику приложения. И будет он занимать 1000+ строк.
Хотя это конечно ваше дело, видимо вы или совсем не работали в команде. Или делали небольшие приложения, которые вы лично и поддерживали, и вам не приходилось каждые полгода объяснять новым членам команды, что означают все эти иксы и игреки. И в каком именно игреке надо искать код, который нужно поправить для решения задачи. Можно даже найти плюс в таком подходе, не надо ломать голову над одной из сложнейших задач программирования — придумывание названий.
Для меня же ваш код выглядит как результат работы обфуксатора или минимайзера. Там примерно такие же названия функций из 1-2 символов.
Говоря про "абстрактный" автомат, я типа намекнул, что неплохо бы добавить конкретики. Какого именно генератора? Что он должен делать? А не просто "граф генератора", как будто у любого генератора и автомата есть граф по умолчанию.
Я могу показать вам код sleep в виде генератора. Думается мне что граф у него точно такой же как у вашего PSleep, зато читается и понимается в разы проще, и кода у него сильно меньше:
А вот так он будет выглядеть если его сделать без "сахара":
Согласитесь — это практически тоже самое, что ваш PSleep. Можете даже вынести проверку условия в отдельный метод и назвать его x1(), если вы пишете код лично для себя.