Статьи

Функциональное реактивное программирование в дротике

Функциональное реактивное программирование (FRP) — это парадигма моделирования взаимодействий в реактивном стиле. Это обеспечивает богатую композицию и позволяет тестировать сложные взаимодействия. Шон Кирби и я собрали небольшой прототип библиотеки (доступный по адресу: https://github.com/vsavkin/frp_dart ), чтобы показать, как эту парадигму можно использовать в Dart.

Что такое функционально-реактивное программирование?

Как я уже сказал, это парадигма программирования, основанная на следующем:

  1. Понятие о потоках, которые моделируют последовательности дискретных событий.
  2. Понятие свойств, которые моделируют непрерывные значения.

Примером потока может служить случай, когда все нажатия клавиш запускаются на каком-либо элементе ввода. Текст входного элемента может быть смоделирован как свойство.

Добавление свойств

Поскольку Dart SDK предоставляет нам только одну из двух основных абстракций FRP (потоков), мы должны реализовать это свойство самостоятельно.

abstract class Property {
  //the current value of the property at any given moment in time
  get value;

  //the stream you can subscribe to to be notified when the property changes
  Stream changes;

  //a shortcut to attach a listener to the changes stream
  void onChange(Function listener);

  //broadcasts the current values to all the listeners
  void notify();

  //creates a derived property
  Property derive(Function mapper);
}

Создание свойств

Свойство может быть создано одним из следующих способов:

Использование константы

var propertyThatIsAlways10 = fromConst(10)

Использование начального значения и потока

targetValue(_) => _.target.value;
var login = fromStream("", loginField.onKeyUp.map(targetValue));

Получая собственность

bool isPresent(String _) => _.trim().isNotEmpty;
var loginPresent = login.derive(isPresent);

Используя функцию

var coin = new ComputedProperty(() => new Random().nextInt(1));

Объединение свойств

Что действительно делает FRP интересным, так это то, что свойства могут состоять из других свойств. Одним из примеров было бы объединение их вместе.

var password = fromStream("", passwordField.onKeyUp.map(targetValue));
var passwordConfirmation = fromStream("", passwordConfirmationField.onKeyUp.map(targetValue));

var tuples = join([password, passwordConfirmation]);

В начале, значение свойства кортежей: ["", ""]. Если я введу a в поле пароля, значение изменится на ["a", ""]. Аналогично, после ввода b в поле подтверждения, значение станет ["a", "b"].

Есть куча комбинаторов, которые будут использоваться практически при любом взаимодействии. Одним из них является and.

var loginPresent = ...;
var passwordPresent = ...;
var bothFieldsPresent = and([loginPresent, passwordPresent]);

пример

Теперь, пройдя основы функционально-реактивного программирования, давайте посмотрим, как мы можем использовать его для моделирования простого взаимодействия.

Предположим, у нас есть форма регистрации.

Форма регистрации

Мы хотели бы включить кнопку, когда поле входа в систему присутствует, поле пароля присутствует, а поля пароля и подтверждения совпадают.

Прежде всего, давайте определим интерфейс формы.

abstract class SignUpForm {
  Stream<String> get loginFieldValues;
  Stream<String> get passwordFieldValues;
  Stream<String> get confirmationFieldValues;
  void toggleButton(bool enabled);
}

Это отделяет специфичный для DOM код от самого взаимодействия, что позволяет при необходимости заменять реальную форму тестовым двойным.

Во-вторых, импортируйте библиотеку FRP.

import 'package:frp/frp.dart' as _;

Далее реализуем само взаимодействие.

signUpInteraction(SignUpForm form){
  var password = _.fromStream("", form.passwordFieldValues);
  var passwordPresent = password.derive(isPresent);

  var confirmation = _.fromStream("", form.confirmationFieldValues);
  var passwordConfirmed = _.same([password, confirmation]);

  var login = _.fromStream("", form.loginFieldValues);
  var loginPresent = login.derive(isPresent);

  var validForm = _.and([loginPresent, passwordPresent, passwordConfirmed]);

  validForm.onChange(form.toggleButton);
}

Наконец, нам нужно создать экземпляр формы и вызвать взаимодействие.

class DomSignUpForm implements SignUpForm {
  Element form;
  DomSignUpForm(this.form);

  get loginFieldValues         => _fieldValues("input[name=login]");
  get passwordFieldValues      => _fieldValues("input[name=password]");
  get confirmationFieldValues  => _fieldValues("input[name=confirmation]");
  toggleButton(enabled)        => form.query("input[type=button]").disabled = !enabled;

  _fieldValues(selector) => form.query(selector).onKeyUp.map((_) => _.target.value);
}

main(){
  signUpInteraction(new DomSignUpForm(query("form")));
}

Завершение

  • Функциональное реактивное программирование может использоваться для выражения сложных взаимодействий пользовательского интерфейса декларативным способом.

  • Свойства могут быть созданы из потоков и получены из других свойств. Кроме того, их можно комбинировать с использованием таких комбинаторов, как join.

  • Библиотека, представленная в этой статье, является всего лишь прототипом, но, поскольку Dart SDK предоставляет потоки, создание готовой к использованию библиотеки, подобной Bacon.js, не должно быть очень трудным.

Узнайте больше о дартс

Узнайте больше о Дарт на сайте engineering.nulogy.com .