"Reactive Extensions" — это больше, чем фреймворк. Хотя бы потому, что существует практически идентичная реализация (с поправкой на особенности конкретного языка и соответствующих практик оптимизации) под каждый популярный ЯП. Есенин утверждает, что «большое видится на расстояньи». В этой записке я буду отходить на разные «расстоянья» и описывать то, что видится мне.
Вижу push-версию классической реализации Iterator'a GoF. Я уже писал об этом, поэтому без подробностей.
На этом расстоянии ясно виднеется различие между push- и pull-системами. После такого озарения каждый git push и git pull вызывает чуть ли не благоговейный трепет. Начинаешь шариться по коду и задаваться сакральными вопросами о дуалах.
«Нечто продолжается» (метод next), «нечто завершилось» (метод completed), «все пошло не так» (метод error) — три утверждения, которыми можно описать любой процесс, развивающийся во времени. Причем от физического времени абстрагироваться легко, заменив его «последовательностью состояний» (в которых оказывалась система). Rx позволяет свести самые разнообразные алгоритмы к единому интерфейсу (в смысле «договоренности» программиста с другими программистами и, что еще важнее, с машиной), при этом не накладывая ограничений ни на выразительность (число возможных состояний), ни на (по желанию: синхронное, асинхронное или мультипоточное) исполнение.
Отсюда следует важнейший вывод: один rx — один процесс. А если сложный процесс состоит из n подпроцессов, то… один rx «высшего порядка», который управляет работой n rx'ов «первого порядка». По аналогии с функциями высшего порядка.
По аналогии с функциями? Да, функциями. Последнее и самое мощное озарение в том, что rx — это просто декорация функции — программистской, а не математической: последние живут вне времени; обычная функция способна совершить return result; только один раз. А next(result); — это «многоразовая» версия return'а. Отсюда важнейший вывод: все, что можно делать с математическими (чистыми) и обычными ООПшными функциями (в том числе и каррирование, и композиция, и много чего другого, чему данный очерк не посвящен), можно делать и с rx. Функции бывают блокирующими и асинхронными: rx тоже. Функции могут возвращать функции: rx тоже. Функции могут быть рекурсивными: rx тоже. Вычисления функций можно кешировать: в rx тоже.
Любопытно, что на этом этапе понимания невольно возвращаешься в… функциональное программирование. Не ради декларативности, не ради иммутабельности — это все (необязательные) бонусы. В «функциональное» потому что вынужден думать в терминах функций и их композиций; а по непреложному закону «один rx — один процесс», именно функции (а не классы, абстрактные классы, интерфейсы или что там еще есть) становятся «отправной точкой» в проектировании.
У меня всё.
Дзен первый
Вижу push-версию классической реализации Iterator'a GoF. Я уже писал об этом, поэтому без подробностей.
Краткий пересказ для тех, кому лень читать
Смысл в том, что Observer — это (почти) «зеркальное отражение» классической реализации Iterator'а. Почему «почти» — объяснено в посте по ранее данной ссылке. Важное замечание: «зеркальное отражение» — это без пяти минут математическое определение и его можно формализовать строго.
На этом расстоянии ясно виднеется различие между push- и pull-системами. После такого озарения каждый git push и git pull вызывает чуть ли не благоговейный трепет. Начинаешь шариться по коду и задаваться сакральными вопросами о дуалах.
Дзен второй
«Нечто продолжается» (метод next), «нечто завершилось» (метод completed), «все пошло не так» (метод error) — три утверждения, которыми можно описать любой процесс, развивающийся во времени. Причем от физического времени абстрагироваться легко, заменив его «последовательностью состояний» (в которых оказывалась система). Rx позволяет свести самые разнообразные алгоритмы к единому интерфейсу (в смысле «договоренности» программиста с другими программистами и, что еще важнее, с машиной), при этом не накладывая ограничений ни на выразительность (число возможных состояний), ни на (по желанию: синхронное, асинхронное или мультипоточное) исполнение.
Отсюда следует важнейший вывод: один rx — один процесс. А если сложный процесс состоит из n подпроцессов, то… один rx «высшего порядка», который управляет работой n rx'ов «первого порядка». По аналогии с функциями высшего порядка.
Дзен третий
По аналогии с функциями? Да, функциями. Последнее и самое мощное озарение в том, что rx — это просто декорация функции — программистской, а не математической: последние живут вне времени; обычная функция способна совершить return result; только один раз. А next(result); — это «многоразовая» версия return'а. Отсюда важнейший вывод: все, что можно делать с математическими (чистыми) и обычными ООПшными функциями (в том числе и каррирование, и композиция, и много чего другого, чему данный очерк не посвящен), можно делать и с rx. Функции бывают блокирующими и асинхронными: rx тоже. Функции могут возвращать функции: rx тоже. Функции могут быть рекурсивными: rx тоже. Вычисления функций можно кешировать: в rx тоже.
Любопытно, что на этом этапе понимания невольно возвращаешься в… функциональное программирование. Не ради декларативности, не ради иммутабельности — это все (необязательные) бонусы. В «функциональное» потому что вынужден думать в терминах функций и их композиций; а по непреложному закону «один rx — один процесс», именно функции (а не классы, абстрактные классы, интерфейсы или что там еще есть) становятся «отправной точкой» в проектировании.
У меня всё.
MetaAbstract
это сатори.