Сравнение core.async и функционального реактивного программирования (+ Rx)
Кажется, я немного смущен при сравнении Clojure core.async с так называемыми реактивными расширениями (Rx) и FRP в общем. Они, похоже, решают подобную проблему асинхронности, поэтому я задаюсь вопросом, каковы главные различия и в каких случаях один предпочтительнее другого. Может кто-нибудь объяснить?
РЕДАКТИРОВАТЬ: Чтобы стимулировать более подробные ответы, я хочу уточнить вопрос:
-
Core.async позволяет писать синхронный код. Однако, как я понимаю, FRP требует только один уровень вложенных обратных вызовов (вся функция, обрабатывающая логику, передается как аргументы API FRP). Похоже, что оба подхода делают ненужные пирамиды обратного вызова. Это правда, что в JS мне приходится писать function() {...}
много раз, но основная проблема, вложенные обратные вызовы, также удалена в FRP. Правильно ли я понимаю?
-
" FRP собирает сообщения сообщений с потоком управления" Можете ли вы (кто-то) дать более конкретное объяснение?
-
Не могу ли я передавать наблюдаемые конечные точки FRP так же, как я пропускаю каналы?
В общем, я понимаю, откуда берутся оба подхода, и я попробовал несколько учебников в обоих из них. Однако я, кажется, "парализован" неочевидностью различий. Есть ли какой-нибудь пример кода, который трудно записать в одном из них и легко использовать другой? И какова архитектурная причина этого?
Ответы
Ответ 1
Я думаю, что основная проблема заключается в том, что ваше предположение о решаемой проблеме не совсем так, поскольку ни одна из них не занимается проблемой асинхронности.
Абстракции
FRP
Основная идея - это распространение изменений, подумайте о том, чтобы сделать то же самое, что делает Excel, где вы определяете ячейки в зависимости друг от друга в каскаде, и когда одна ячейка изменяется, все зависимые ячейки на каскаде пересчитываются.
core.async
Основная идея - это системная декомпозиция, думайте как разделяющие проблемы, используя queue
в середине различных процессов, а в случае core.async
вместо очередей у вас есть каналы, но вы получаете идею.
Таким образом, удаление пирамидального кода не является целью любой технологии, и они работают на разных уровнях абстракции.
О выборе потока управления
Идея о компиляции и управлении потоком взята из исходного сообщения асинхронного ядра.
Несмотря на то, что существуют различные механизмы для очистки событий/обратных вызовов (FRP, Rx/Observables), они не меняют своей фундаментальной природы, а именно, что при событии выполняется произвольное количество другого кода, возможно, в том же потоке, что приводит к предупреждениям, таким как "не делайте слишком много работы в обработчике" и фразы типа "callback hell".
Перефразируя, если у вас есть код бизнес-домена внутри обработчика событий, вы собрали обработку событий X с помощью , что делать, когда X происходит.
Это то, что решает core.async
, так как введение очереди/канала посередине помогает улучшить разделение проблем.
Реализация
Все ваши вопросы, касающиеся обратных вызовов и передачи наблюдаемых конечных точек в качестве параметров, являются только вопросами реализации, это действительно зависит от реализации Rx
и API.
Если вы посмотрите на React повторно используемые компоненты, у вас действительно нет большого количества обратного ада, и вы получите эту идею прохождение наблюдаемых вокруг.
Заключительные мысли
Даже если Rx
можно использовать для моделирования любого потока данных, более часто используется для UI Rendering a-la Excel, чтобы упростить изменение вашего представления при изменении вашей модели.
С другой стороны, core.async
может использоваться для моделирования разделения проблем, когда любые две подсистемы взаимодействуют друг с другом (такой же сценарий использования, что и очереди), используя его в основной цепочке рендеринга интерфейса, заключается в разделении:
- Генерация и обработка событий на стороне сервера
- Как это событие влияет на вашу модель.
Таким образом, вы можете иметь core.async
и FRP
вместе, так как core.async
будет разделять проблемы, а FRP
будет определять ваш каскадный поток данных после обновления вашей модели.
Ответ 2
По крайней мере одно из основных принципиальных различий и, я думаю, то, что Rich с помощью "[FRP] составляет сообщение сообщений с потоком управления", заключается в следующем.
Обратные вызовы - это код, который выполняется. И это выполнение должно произойти в некотором потоке в определенный момент времени. Часто время, когда происходит событие, и поток - это поток, который замечает/создает событие. Если производитель вместо этого отправляет сообщение на канал, вы можете использовать это сообщение, когда захотите, в любом потоке, который вы хотите. Таким образом, обратные вызовы, которые по существу являются формой связи, составляют связь с потоком управления, определяя, когда и где выполняется код обратного вызова. Если вам нужно использовать обратные вызовы по какой-либо причине, просто используйте их, чтобы поместить сообщение в очередь/канал, и вы вернулись на дорожку.
Поскольку core.async менее сложный, он должен быть предпочтительным, если нет оснований для использования альтернатив.
Ответ 3
Clojure core.async - это портовый блок go на языке Go. Основной концепцией являются каналы, представляющие асинхронную связь между потоками.
Цель состоит в том, чтобы иметь возможность писать нормально выглядящие блоки последовательного кода, которые получают доступ к каналам для ввода данных, и это легко переводится в конечный автомат, который работает CSP для вас.
Контрастность с FRP, которая по-прежнему принципиально связана с обратными вызовами, и она объединяет сообщения сообщений с потоком управления. core.async полностью исключает обратные вызовы из вашего кода и отделяет поток управления от передачи сообщений. Кроме того, в FRP канал не является объектом первого класса (т.е. Вы не можете отправлять FRP-канал в качестве значения на канале FRP).
Ответ 4
EDIT:
Чтобы ответить на ваши вопросы:
-
Да, много раз обратные вызовы могут быть устранены, например, с помощью логики повтора, distinctUntilChanged и множества других вещей, таких как:
var obs = getJSON('story.json').retry(3);
var obs = Rx.Observable.fromEvent(document, 'keyup').distinctUntilChanged();
-
Я не уверен, что это означает, что это усложняет управление потоком.
-
Да, вы можете обойти объект, как если бы вы использовали канал, как и для объекта ниже.
Например, здесь вы можете использовать канал как поведение, используя RxJS с ReplaySubject:
var channel = new Rx.ReplaySubject();
// Send three observables down the chain to subscribe to
channel.onNext(Rx.Observable.timer(0, 250).map(function () { return 1; }));
channel.onNext(Rx.Observable.timer(0, 1000).map(function () { return 2; }));
channel.onNext(Rx.Observable.timer(0, 1500).map(function () { return 3; }));
// Now pass the channel around anywhere!
processChannel(channel);
Чтобы сделать это немного более конкретным, сравните код из сообщения Дэвида Нолена здесь с примером FRP RxJS здесь
Ответ 5
Здесь есть сообщение, в котором сравнивается FRP с CSP по ограниченному набору примеров (что, однако, предназначено для демонстрации преимуществ CSP), с заключением в конце: http://potetm.github.io/2014/01/07/frp.html