Подтвердить что ты не робот

Как реализовать шаблон проектирования Observer в чисто функциональном режиме?

Скажем, я хочу реализовать шину событий с использованием языка программирования OO. Я мог бы сделать это (псевдокод):

class EventBus

    listeners = []

    public register(listener):
        listeners.add(listener)

    public unregister(listener):
        listeners.remove(listener)

    public fireEvent(event):
        for (listener in listeners):
            listener.on(event)

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

Как бы вы реализовали этот шаблон с использованием языка функционального программирования (такого как один из вариантов lisp)?

Я спрашиваю об этом, потому что, если вы не используете объекты, для поддержания коллекции всех слушателей все равно потребуется какое-то состояние. Более того, поскольку коллекция слушателей меняется со временем, было бы невозможно создать чисто функциональное решение, верно?

4b9b3361

Ответ 1

Некоторые замечания по этому поводу:

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

Также шаблон наблюдателя обычно используется для уведомления изменений состояния, как в различных реализациях MVC. Однако на функциональном языке нет прямого способа делать изменения состояния, если вы не используете некоторые трюки, такие как монады для имитации состояния. Однако, если вы имитируете изменения состояния с помощью монад, вы также получите точки, где вы можете добавить механизм наблюдателя внутри монады.

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

Ответ 2

Если шаблон Observer по существу относится к издателям и подписчикам, то Clojure имеет несколько функций, которые вы могли бы использовать:

Функция add-watch принимает три аргумента: ссылку, функциональный ключ часов и функцию часов, вызываемую при изменении состояния ссылки.

Очевидно, что из-за изменений в изменяемом состоянии это не является чисто функциональным (как вы явно просили), но add-watcher даст вам способ реагировать на события, если тот эффект, который вы искали, вот так:

(def number-cats (ref 3))

(defn updated-cat-count [k r o n]
  ;; Takes a function key, reference, old value and new value
  (println (str "Number of cats was " o))
  (println (str "Number of cats is now " n)))

(add-watch number-cats :cat-count-watcher updated-cat-count)

(dosync (alter number-cats inc))

Вывод:

Number of cats was 3
Number of cats is now 4
4

Ответ 3

Более того, поскольку коллекция слушателей изменяется со временем, невозможно было бы создать чисто функциональное решение, верно?

Это не проблема - в общем, всякий раз, когда вы изменяете атрибут объекта в императивном решении, вы можете вычислить новый объект с новым значением в чисто функциональном решении. Я считаю, что фактическое распространение событий является немного более проблематичным - оно должно быть реализовано функцией, которая принимает событие, весь набор потенциальных наблюдателей плюс EventBus, а затем отфильтровывает фактических наблюдателей и возвращает совершенно новый набор объектов с новыми состояниями наблюдателей, вычисленными их функциями обработки событий. Конечно, не наблюдатели будут одинаковыми во входных и выходных наборах.

Интересно, если эти наблюдатели генерируют новые события в ответ на их методы on (здесь: функции), которые вызываются - в этом случае вам нужно применить функцию рекурсивно (возможно, позволить ей принимать более одного события) до тех пор, пока он не производит больше событий для обработки.

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

TL; DR: Я считаю, что распространение событий моделирования чисто функциональным способом сложно.

Ответ 4

Я бы предложил создать ref, который содержит набор слушателей, каждая из которых является функцией, которая действует на событие.

Что-то вроде:

(def listeners (ref #{}))

(defn register-listener [listener]
  (dosync
     (alter listeners conj listener)))

(defn unregister-listener [listener]
  (dosync
     (alter listeners disj listener)))

(defn fire-event [event] 
  (doall
    (map #(% event) @listeners)))

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

Обратите внимание на комментарий C.A.McCann: Я использую "ref", в котором хранится набор активных слушателей, у которого есть отличное бонусное свойство, что решение безопасно для concurrency. Все обновления осуществляются защитой транзакции STM в структуре (dosync....). В этом случае он может быть переполнен (например, атом также будет делать трюк), но это может пригодиться в более сложных ситуациях, например. когда вы регистрируете/отменяете регистрацию сложного набора слушателей и хотите, чтобы обновление выполнялось в одной, потокобезопасной трансакции.