27

Let's say I want to implement an event bus using a OO programming language. I could do this (pseudocode):

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)

This is actually the the observer pattern, but used for event-driven control flow of an application.

How would you implement this pattern using a functional programming language (such as one of the lisp flavors)?

I ask this because if one doesn't use objects, one would still need some kind of state to maintain a collection of all the listeners. More over, since the listeners collection changes over time, it would not be possible to create a pure functional solution, right?

ivo
  • 4,101
  • 5
  • 33
  • 42

4 Answers4

30

Some remarks on this:

I am not sure how it is done, but there is something called "functional reactive programming" which is available as a library for many functional languages. This is actually more or less the observer pattern done right.

Also the observer pattern is usually used for notifying changes in state, as in the various MVC implementations. However in a functional language there is no direct way to do state-changes, unless you use some tricks such as monads to simulate the state. However if you simulate the state changes using monads you will also get points where you can add the observer mechanism inside the monad.

Judging from the code you posted it seems that you are actually doing event driven programming. So the observer pattern is a typical way to get event driven programming in Object oriented languages. So you have a goal (event driven programming) and a tool in the object oriented world (observer pattern). If you want to use the full power of functional programming you should check what other methods are available for achieving this goal instead of directly porting the tool from the object oriented world (it might not be the best choice for a functional language). Just check what other tools are available here and you will probably find something that fits your goals much better.

Sridhar Ratnakumar
  • 81,433
  • 63
  • 146
  • 187
LiKao
  • 10,408
  • 6
  • 53
  • 91
  • 1
    +1 for a good answer, but note that Clojure does have some very powerful ways of managing mutable state while staying within the functional paradigm (i.e. your comments are more applicable to "pure" functional languages such as Haskell) – mikera Aug 05 '11 at 12:01
  • 1
    @mikera: Haskell has plenty of ways to manage mutable state as well, and I don't know where people get the idea it doesn't. It just requires effect tracking and explicit ordering, which has more to do with laziness than anything else. FRP is interesting mostly because it's just conceptually *nicer* than using mutable state. – C. A. McCann Aug 05 '11 at 13:31
  • 2
    @C A McCann - when I last used Haskell (admittedly a few years ago) everything that involved mutable state in Haskell had to be done with monadic constructs, in order to convert the concept of mutability into a pure function. Has that changed? e.g. If I have a function Int -> Int can it have a mutable side effect? You can in Clojure, to my most recent knowledge you can't in Haskell, but happy to learn otherwise. – mikera Aug 05 '11 at 13:39
  • 2
    @mikera: Like I said, it's really just effect tracking and explicit ordering; the monadic API is an implementation detail (yes, you can *simulate* state in pure code with the `State` monad, but that's a completely different thing from true mutable state). The philosophy [Clojure uses](http://clojure.org/state) is almost identical in spirit to Haskell, minus the static types--just read `IO a` as being an abstract identity, with a value of type `a`. Side effects in a function `Int -> Int` would be like modifying a value directly in Clojure. – C. A. McCann Aug 05 '11 at 14:28
26

If the Observer pattern is essentially about publishers and subscribers then Clojure has a couple of functions that you could use:

The add-watch function takes three arguments: a reference, a watch function key, and a watch function that is called when the reference changes state.

Clearly, because of the changes in mutable state, this is not purely functional (as you clearly requested), but add-watcher will give you a way to react to events, if that's the effect you were seeking, like so:

(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))

Output:

Number of cats was 3
Number of cats is now 4
4
Scott
  • 17,127
  • 5
  • 53
  • 64
7

I'd suggest creating a ref which contains a set of listeners, each of which is a function that acts on an event.

Something like:

(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)))

Note that you are using mutable state here, but that is OK because the problem you are trying to solve explicitly requires state in terms of keeping track of a set of listeners.

Note thanks to C.A.McCann's comment: I'm using a "ref" which stores the set of active listeners which has the nice bonus property that the solution is safe for concurrency. All updates take place protected by the STM transaction within the (dosync ....) construct. In this case it's possibly overkill (e.g. an atom would also do the trick) but this might come in handy in more complex situations, e.g. when you are registering/unregistering a complex set of listeners and want the update to take place in a single, thread-safe transation.

mikera
  • 105,238
  • 25
  • 256
  • 415
  • `ref` gives you a transactional mutable reference based on STM, doesn't it? You should probably mention that, since it means your example should automatically work safely and correctly in a concurrent setting. – C. A. McCann Aug 05 '11 at 13:38
  • @C A McCann - that's right. I can see a "fun" situation where you need to simultaneously remove one listener and add another while guaranteeing that an incoming event on another thread only goes to one (but not both or neither) of the listeners. As long as you make this update in a transaction then Clojure's STM will automatically solve this problem for you, which is nice! – mikera Aug 05 '11 at 13:49
  • Yes, that's exactly the sort of situation I had in mind. A very fast operation that should be atomic leads to pernicious intermittent concurrency bugs when the assumption is very infrequently violated, but with STM it just works painlessly. I think having it easily available should be a major selling point for Clojure. Using STM in Haskell doesn't add any extra complexity or syntactic overhead, and my impression is that it's similarly trivial to use in Clojure. – C. A. McCann Aug 05 '11 at 14:04
  • 1
    Since you only have a single ref, you could also just use and atom and `swap!`. You only need STM for coordinating change of multiple refs. – Dave Ray Aug 06 '11 at 19:08
6

More over, since the listeners collection changes over time, it would not be possible to create a pure functional solution, right?

This is less of a problem - in general, whenever you'd modify an object's attribute in an imperative solution, you can compute a new object with the new value in a pure functional solution. I believe that the actual event propagation is a bit more problematic - it would have to be implemented by a function that takes the event, the whole set of potential observers plus the EventBus, then filters out the actual observers and returns a whole new set of objects with the new states of observers computed by their event processing functions. Non-observers would of course be the same in the input and output sets.

It gets interesting if those observers generate new events in response to their on methods (here: functions) being called - in this case you need to apply the function recursively (perhaps allowing it to take more than one event) until it produces no more events to process.

In general, the function would take an event and a set of objects and return the new set of objects with new states representing all modifications resulting from the event propagation.

TL;DR: I think that modeling event propagation in a pure functional way is complicated.

Rafał Dowgird
  • 43,216
  • 11
  • 77
  • 90