Сравнение core.async и функционально-реактивного программирования (+Rx)

Я, кажется, немного запутался, сравнивая core.async Clojure с так называемыми Reactive Extensions (Rx) и FRP в целом. Похоже, что они решают аналогичную проблему асинхронности, поэтому мне интересно, каковы основные различия и в каких случаях одно предпочтение перед другим. Может кто-нибудь объяснить, пожалуйста?

РЕДАКТИРОВАТЬ: Чтобы поощрить более подробные ответы, я хочу сделать вопрос более конкретным:

  1. Core.async позволяет мне писать синхронно выглядящий код. Однако, насколько я понимаю, FRP нужен только один уровень вложенных обратных вызовов (все функции, которые обрабатывают логику, передаются в качестве аргументов в FRP API). Кажется, что оба подхода делают пирамиды обратного вызова ненужными. Это правда, что в JS я должен написать function() {...} много раз, но основная проблема, вложенные обратные вызовы, исчезла и в FRP. Я правильно понял?

  2. "FRP объединяет передачу сообщений с потоком управления". Не могли бы вы (кто-то) дать более конкретное объяснение?

  3. Разве я не могу обойти наблюдаемые конечные точки 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).

РЕДАКТИРОВАТЬ:

Чтобы ответить на ваши вопросы:

  1. Да, много раз, обратные вызовы могут быть устранены, например, с помощью логики повторных попыток, DifferentUntilChanged и многих других вещей, таких как:

    var obs = getJSON('story.json').retry(3);

    var obs = Rx.Observable.fromEvent(document, 'keyup').distinctUntilChanged();

  2. Я не уверен, к чему это относится с усложнением управления потоком.

  3. Да, вы можете обойти объект так же, как и канал, как в случае с объектом ниже.

Например, вы можете иметь поведение, подобное каналу, используя 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

Другие вопросы по тегам