Сравнение core.async и функционально-реактивного программирования (+Rx)
Я, кажется, немного запутался, сравнивая core.async Clojure с так называемыми Reactive Extensions (Rx) и FRP в целом. Похоже, что они решают аналогичную проблему асинхронности, поэтому мне интересно, каковы основные различия и в каких случаях одно предпочтение перед другим. Может кто-нибудь объяснить, пожалуйста?
РЕДАКТИРОВАТЬ: Чтобы поощрить более подробные ответы, я хочу сделать вопрос более конкретным:
Core.async позволяет мне писать синхронно выглядящий код. Однако, насколько я понимаю, FRP нужен только один уровень вложенных обратных вызовов (все функции, которые обрабатывают логику, передаются в качестве аргументов в FRP API). Кажется, что оба подхода делают пирамиды обратного вызова ненужными. Это правда, что в JS я должен написать
function() {...}
много раз, но основная проблема, вложенные обратные вызовы, исчезла и в FRP. Я правильно понял?"FRP объединяет передачу сообщений с потоком управления". Не могли бы вы (кто-то) дать более конкретное объяснение?
Разве я не могу обойти наблюдаемые конечные точки FRP так же, как я пропускаю каналы?
В целом, я понимаю, откуда оба подхода исторически сложились, и я попробовал несколько учебных пособий в обоих из них. Однако я, кажется, "парализован" неочевидностью различий. Есть ли какой-нибудь пример кода, который было бы трудно написать в одном из них и легко использовать в другом? И какова архитектурная причина этого?
5 ответов
Я думаю, что главная проблема заключается в том, что ваше предположение о решаемой проблеме не совсем так, поскольку никто из них не решает асинхронную "проблему".
Абстракции
FRP
Основная идея - распространение изменений, подумайте о том, чтобы выполнить то же самое, что делает Excel, где вы определяете ячейки, зависящие друг от друга в каскаде, и когда одна ячейка изменяется, все зависимые ячейки в каскаде пересчитываются.
core.async
Основная идея заключается в разложении систем, думать как разделение интересов с помощью queue
в середине различных процессов, в core.async
В случае вместо очередей у вас есть каналы, но вы поняли идею.
Таким образом, удаление пирамидального кода не является целью ни одной из технологий, и они работают на разных уровнях абстракции.
О составлении потока управления
Идея об установлении связи и управлении потоком взята из оригинального основного асинхронного сообщения.
Хотя существуют различные механизмы для того, чтобы сделать события / обратные вызовы более чистыми (FRP, Rx/Observables), они не меняют своей фундаментальной природы, которая заключается в том, что при событии запускается произвольное количество другого кода, возможно, в том же потоке, что приводит к предостережения, такие как "не делайте слишком много работы в вашем обработчике", и такие фразы, как "ад обратного вызова".
Перефразируя, если у вас есть код бизнес-домена внутри обработчика событий, вы завершили обработку события X с тем, что делать, когда X происходит.
Который является то, что core.async
Решения, поскольку введение очереди / канала в середине, помогает лучше разделить проблемы.
Реализация
Все ваши вопросы, касающиеся обратных вызовов и передачи наблюдаемых конечных точек в качестве параметров, являются просто вопросами реализации, это действительно зависит от Rx
реализация и API.
Если вы посмотрите на компоненты React для повторного использования, у вас на самом деле не будет ада обратного вызова, и вы получите представление о том, как передавать наблюдаемые.
Последние мысли
Даже если Rx
может использоваться для моделирования любого потока данных; чаще используется для визуализации пользовательского интерфейса в формате Excel, чтобы упростить процесс обновления представления при изменении модели.
С другой стороны, Core.Async
может использоваться для моделирования разделения проблем, когда любые две подсистемы взаимодействуют друг с другом (тот же сценарий использования, что и для очередей), используя его в цепочке рендеринга пользовательского интерфейса. Основная идея заключается в разделении:
- Генерация и обработка событий на стороне сервера
- Как это событие влияет на вашу модель
Таким образом, вы можете иметь core.async
а также FRP
вместе с core.async
будет разделять проблемы, и FRP
определит ваш каскадный поток данных после обновления вашей модели.
По крайней мере, одно из основных принципиальных отличий и, как мне кажется, то, что Ричмент от "[FRP] связывает передачу сообщений с потоком управления", заключается в следующем.
Обратные вызовы - это код, который выполняется. И это выполнение должно произойти в каком-то потоке в определенный момент времени. Часто время наступает, когда событие происходит, и поток - это поток, который замечает / производит событие. Если производитель вместо этого разместит сообщение на канале, вы можете использовать его, когда захотите, в любом потоке, который хотите. Таким образом, обратные вызовы, которые, по сути, являются формой связи, объединяют связь с потоком управления, диктуя, когда и где выполняется код обратного вызова. Если по какой-либо причине вам необходимо использовать обратные вызовы, просто используйте их, чтобы поместить сообщение в очередь / канал, и вы вернетесь на правильный путь.
Поскольку core.async является менее сложным, он должен быть предпочтительным, если нет веских причин для использования альтернатив.
Clojure core.async - это порт блоков go из языка Go. Фундаментальная концепция - это каналы, представляющие асинхронную связь между потоками.
Цель состоит в том, чтобы иметь возможность писать нормально выглядящие блоки последовательного кода, которые обращаются к каналам для получения ввода, и это беспрепятственно переводится в конечный автомат, который разрабатывает перевод CSP для вас.
Контраст с FRP, который по-прежнему в основном о обратных вызовах, и он объединяет обмен сообщениями с потоком управления. core.async полностью исключает обратные вызовы из вашего кода и отделяет поток управления от передачи сообщений. Кроме того, в FRP канал не является объектом первого класса (т. Е. Вы не можете отправить канал FRP в качестве значения на канал FRP).
РЕДАКТИРОВАТЬ:
Чтобы ответить на ваши вопросы:
Да, много раз, обратные вызовы могут быть устранены, например, с помощью логики повторных попыток, DifferentUntilChanged и многих других вещей, таких как:
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);
Чтобы сделать это немного более конкретным, сравните код из поста Дэвида Нолена здесь с примером FRX RxJS здесь
Здесь есть пост, в котором сравнивается FRP с CSP на ограниченном наборе примеров (который, однако, должен продемонстрировать преимущества CSP), с заключением в конце: http://potetm.github.io/2014/01/07/frp.html