Чисто функциональное подавление обратной связи?

#functional-programming #state #stateless

#функциональное программирование #состояние #без состояния

Вопрос:

У меня есть проблема, которую я могу решить достаточно легко с помощью классического императивного программирования с использованием состояния: я пишу приложение для совместного просмотра, которое разделяет URL-адреса между несколькими узлами. В программе есть модуль для связи, который я вызываю link , и для обработки браузера, который я вызываю browser . Теперь, когда поступает URL link -адрес, я использую browser модуль, чтобы сообщить фактическому веб-браузеру начать загрузку URL-адреса.

Фактический браузер запустит навигационное обнаружение того, что входящий URL-адрес начал загружаться, и, следовательно, будет немедленно представлен в качестве кандидата для отправки другой стороне. Этого следует избегать, поскольку это создало бы бесконечный цикл перехода по ссылкам на один и тот же URL-адрес в соответствии со следующим (очень концептуализированным) псевдокодом (это Javascript, но, пожалуйста, учтите, что это несколько неуместная деталь реализации):

 actualWebBrowser.urlListen.gotURL(function(url) {
  // Browser delivered an URL
  browser.process(url);
});
link.receivedAnURL(function(url) {
  actualWebBrowser.loadURL(url); // will eventually trigger above listener
});
  

Что я сделал сначала, чтобы сохранить каждый входящий URL browser -адрес и просто сразу же использовать URL-адрес при его поступлении, а затем удалить его из списка «полученных» в browser соответствии с этим:

 browser.recents = {} // <--- mutable state
browser.recentsExpiry = 40000;

browser.doSend = function(url) {
  now = (new Date).getTime();
  link.sendURL(url); // <-- URL goes out on the network

  // Side-effect, mutating module state, clumsy clean up mechanism :(
  browser.recents[url] = now;
  setTimeout(function() { delete browser.recents[url] }, browser.recentsExpiry);

  return true;
}
browser.process = function(url) {
  if(/* sanity checks on `url`*/) {
    now = (new Date).getTime();
    var duplicate = browser.recents[url];
    if(! duplicate) return browser.doSend(url);
    if((now - duplicate_t) > browser.recentsExpiry) {
      return browser.doSend(url);
    }
    return false;
  }
}
  

Это работает, но я немного разочарован своим решением из-за моего привычного использования изменяемого состояния browser . Есть ли «лучший способ ™» с использованием неизменяемых структур данных / функционального программирования или тому подобного для подобной ситуации?

Комментарии:

1. Я нашел объяснение вашей проблемы немного сложным для понимания. Может быть, было бы понятнее с помощью некоторого псевдокода, описывающего, что делают модули браузера и ссылок?

2. Достаточно справедливо, попытался проиллюстрировать некоторые концептуальные наброски кода / w о том, что происходит.

Ответ №1:

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

F # ‘s MailboxProcessor является одним из примеров такого подхода. Однако это зависит от того, чтобы обработка выполнялась в независимом потоке, который отличается от стиля вашего кода, управляемого событиями.

Как вы определяете, setTimeout в вашем коде усложняется управление состоянием. Один из способов упростить это — вместо этого browser.process отфильтровать любые URL-адреса с истекшим временем ожидания, прежде чем он сделает что-нибудь еще. Это также устранило бы необходимость дополнительной проверки времени ожидания для конкретного URL-адреса, который он обрабатывает.

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

Например, может быть, вам нужно несколько независимых браузеров? Если это так, вам следует подумать о том, как recents набор может быть инкапсулирован, чтобы принадлежать только одному браузеру, чтобы не возникало коллизий. Даже если вам не нужно несколько для вашего реального приложения, это может помочь тестируемости.

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