Question

I'm struggling to understand how a ReactiveX operator can be considered functional. Operators are implemented as functions, but but with the exception of simple operators like map and reduce many of the operators do not appear to me to be implementable as pure functions.

A simple Buffer operator that buffers 2 items at a time can produce a different output given the same input. The output depends on all previous events - e.g. the first time I pass an event through the operator it emits no events, the second time it emits 2 events:

1)  E -> []
2)  E -> [E,E]

I understand that when considering the entire stream of events - Buffer is conceptually pure i.e. E -> E -> [E, E]. The computation doesn't happen across the entire stream at one time however, it happens event by event and the implementation has to maintain some state in order to be able to provide its output stream.

Is it possible to implement such a Buffer operator in purely functional way? Does it follow that all other 'functional' ReactiveX operators can also be implemented functionally?

Or when we say ReactiveX is functional, we only mean that a the stream level, and not the event. Can a function that is implemented using non-pure functions be pure?

Edit - Adding more detail as requested in comments

A pure function has two properties: From Wikipedia

  • Its return value is always the same for the same arguments
  • Its evaluation has no side effects

In my example - the output of the Buffer can be different for the same inputs - depending on what other events in the stream.

In terms of implementation, a class that would Buffer two items would basically be doing something like the following:

class Buffer2 {
   item = null;

   onNext(next) {
      if(this.item == null) {
         this.item = next;
         return [];
      } else {
         let emit = [this.item, next];
         this.item = null;
         return emit;
      } 
   }
}

The actual implementation of Buffer in Rx is quite different - but I think this captures the essence of what its doing in a 2 item buffer scenario.

To me the onNext(next) function clearly violates the "return value is the same for the same arguments" - the return value depends on whether item is null or not.

Was it helpful?

Solution

Firstly the pedantic argument... You ask how buffer, as an example, could be considered functional, but then go on to talk, not about the buffer function itself, but the onNext and subscribe functions (because you start talking about the events running through the pipe instead of the pipe itself.) As an analogy, the water pipes in a house are completely static in place despite the fact that the water running through them is dynamic. In the same way that the operators are functional despite the data running though them.

Second the monadic argument... As long as what happens in the monad stays in the monad, the monad itself can be considered functional (Haskell's IO monad comes to mind here.) A map function on an Array is functional even if the function is implemented as a for loop internally. To argue against this is to assert that no programming language is functional, because when push comes to shove all of them operate on an inherently imperative machine. In other words, any programming structure that is "functional" at some level of abstraction is "imperative" at a lower level.

Licensed under: CC-BY-SA with attribution
scroll top