Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is RxJS.Observable a monad?

Is Observable really a monad? Does it abide by Monad laws (https://wiki.haskell.org/Monad_laws)? Doesn't seem to me like it does. But maybe my understanding is wrong and somebody can shed some light on the issue. My current reasoning is (I'm using :: to denote "is of kind"):

1) Left identity: return a >>= f ≡ f a

var func = x => Rx.Observable.of(10)  var a = Rx.Observable.of(1).flatMap(func) :: Observable var b = func(1)                           :: ScalarObservable 

HASKELL:

func = (\_ -> putStrLn "B")  do { putStrLn "hello"; return "A" } >>= func   :: IO ()  func "A"                                       :: IO () 

So left identity doesn't hold for Observable. Observable clearly isn't ScalarObservable. In Haskell, the types are the same - IO ().

2) Right identity: m >>= return ≡ m

var x = Rx.Observable.of(1);  x.flatMap(x => Observable.of(x)) :: Observable x                                :: ScalarObservable 

HASKELL:

Just 2 >>= return  :: Num b => Maybe b Just 2             :: Num a => Maybe a 

The same situation as with the left identity. Observable !== ScalarObservable. Whereas in Haskell, the type stays the same, it's a Maybe with a Num inside it.

3) Associativity

(m >>= f) >>= g ≡ m >>= (\x -> f x >>= g)

var x = Rx.Observable.of(10)  var func1 = (x) => Rx.Observable.of(x + 1) var func2 = (x) => Rx.Observable.of(x + 2)   x.flatMap(func1).flatMap(func2)         :: Observable x.flatMap(e => func1(e).flatMap(func2)) :: Observable 

HASKELL:

add2 x = Just(x + 2) add1 x = Just(x + 1)  Just 2 >>= add1 >>= add2             :: Num b => Maybe b Just 2 >>= (\x -> add1(x) >>= add2)  :: Num b => Maybe b 

This is the only law that seems to hold for Observable. But I don't know, maybe this should not be reasoned in the way I did. What do you think?

like image 249
Mateusz Wit Avatar asked Jul 26 '18 15:07

Mateusz Wit


People also ask

What is RxJS observable?

RxJS (Reactive Extensions for JavaScript) is a library for reactive programming using observables that makes it easier to compose asynchronous or callback-based code.

Is observable part of RxJS?

The Observable Class in RxJSRxJS implements observable as a class with a constructor, properties and methods. The most important methods in the observable class are subscribe and pipe : subscribe() lets us subscribe to an observable instance.

Is RxJS asynchronous?

RxJS is a library for composing asynchronous and event-based programs by using observable sequences. It provides one core type, the Observable, satellite types (Observer, Schedulers, Subjects) and operators inspired by Array#extras (map, filter, reduce, every, etc) to allow handling asynchronous events as collections.

What is an observable stream?

What is an Observable? An observable represents a stream, or source of data that can arrive over time. You can create an observable from nearly anything, but the most common use case in RxJS is from events. This can be anything from mouse moves, button clicks, input into a text field, or even route changes.


1 Answers

tldr; Yes.


JavaScript is a dynamic language with duck typing so in runtime, instance of an Observable class is equivalent to an instance of ScalarObservable. RxJS itself is written in TypeScript and these irregularities do not surface up in types and they are - exactly as @Bergi wrote in a comment - an optimisation. On the other hand, you are completely right: in a nominal type system type mismatch could be a real problem and even a compile time error.


Now, answering the question itself - please take a look at a Purescript library with bindings to RxJS:

foreign import data Observable :: Type -> Type  instance monoidObservable :: Monoid (Observable a) where   mempty = _empty  instance functorObservable :: Functor Observable where   map = _map  instance applyObservable :: Apply Observable where   apply = combineLatest id  instance applicativeObservable :: Applicative Observable where   pure = just  instance bindObservable :: Bind Observable where   bind = mergeMap  instance monadObservable :: Monad Observable  -- | NOTE: The semigroup instance uses `merge` NOT `concat`. instance semigroupObservable :: Semigroup (Observable a) where   append = merge  instance altObservable :: Alt Observable where   alt = merge  instance plusObservable :: Plus Observable where   empty = _empty  instance alternativeObservable :: Alternative Observable  instance monadZeroObservable :: MonadZero Observable  instance monadPlusObservable :: MonadPlus Observable  instance monadErrorObservable :: MonadError Error Observable where   catchError = catch  instance monadThrowObservable :: MonadThrow Error Observable where   throwError = throw 

Assuming Purescript types are correct: apart from being a regular Monad, Observable conforms to MonadPlus and MonadError classes. MonadPlus allows to combine computations, while MonadError allows to interrupt or skip some part of computations (in case of Observable we can easily retry computations as well). Observable is not only a monad, but a very powerful one - maybe even the most powerful monad used in the mainstream$.

I do not have any formal proofs, but can shortly describe how to use Observable to model or replace monads describe in https://wiki.haskell.org/All_About_Monads.

Maybe Computations which may not return a result

Non-result can be represented as regular JS undefined or an EMPTY stream.

Error Computations which can fail or throw exceptions

You can throw regular JS errors or return more idiomatic throwError from monadic bind. An error can be catch'ed and then handled or use to retry computations. Throwing an error immediately stops ongoing computations.

List Non-deterministic computations which can return multiple possible results

List is kind of a younger brother of Observable, lacking entirely the time dimension. Anything that can be expressed via operations on a list can be exactly mapped to operations on an observable. You can easily lift a list via Observable.from and downgrade to observable with .toList(). Being native, list performance is going to be much better than observable's. But remember that list is eager and observable lazy, so in some cases observable may outperform list.

IO Computations which perform I/O

Any IO operations (network, disk etc) can be easily wrapped / lifted to the observable world.

State Computations which maintain state

BehaviorSubject

Reader Computations which read from a shared environment

From a consumer perspective it does not matter at all where an instance of Observable comes from. For example: if you declared your config as an observable you can easily change the exact environment from where the value(s) are provided.

Writer Computations which write data in addition to computing values

The simplest option is to return two streams one with values and the other with logs / auxiliary data.

Cont Computations which can be interrupted and restarted

To interrupt computations you can throw an error, use an operator e.g. .switchMap, .takeUntil, explicitly unsubscribe or .mergeMap to EMPTY. Having access to some form of a cache restarting deterministic computations from arbitrary step is pretty trivial: just split your computations to smaller observables and cache their results once computed; when restarted run computations only if cache empty - otherwise use cached value.


If you decide to use observables to represent structure of your computations - you not only can model / replace the most common monads used in practice, but your computations are automatically reactive in flavour. Moreover if you stick to only observable your computations are going to be homogenous, which means there is very little or no need for monad transformers and accidental complexity introduced by them. My working hypothesis is that observable type offers some local (or even global) maximum for expressing structure of asynchronous computations. For example: Observable offers not one, not two, but three! monadic binds with different semantic: mergeMap, switchMap, exhaustMap (if you wonder: concatMap is actually a special case of mergeMap). This very fact on its own is kind of indication that observable is a very interesting mathematical structure.


A bonus

Observable is said to be a stream and streams (in general) are [commonads] (https://bartoszmilewski.com/2017/01/02/comonads/). Does it mean that observable is not only a monad but a comonad as well?

Erik Meijer twit's:

@rix0rrr For a while Rx had a ManySelect operator. Rx is both a monad and a comonad. 144 characters is too short to explain that. Sorry ;-)

like image 93
artur grzesiak Avatar answered Sep 27 '22 18:09

artur grzesiak