DEV Community

Rasmus Schultz
Rasmus Schultz

Posted on

Explain (Timeline) Monads Like I'm Five

I came across this interesting looking library:

It appears to be a simpler alternative to React and hooks - and the author makes it look very appealing in terms of being very simple and generic. It looks like this concept composes well, using plain JS that doesn't require any magic or a linter.

The documentation uses a lot of science jargon though - and the links to articles that explain the concepts are even worse, and the library code has been pretty heavily code-golfed.

I'm not a computer scientist, so...

  • What's special about a timeline monad?

  • How does it differ from a plain old pub/sub event-bus?

  • What's the advantage over pub/sub in the context of UI?

  • Does this somehow help with "unpredictable" events, like, say, chat messages?

Explain like I'm five please? 🙂

Top comments (3)

glebec profile image
Gabriel Lebec

The special thing is that the timeline sync method can itself return new timelines, and the result is a single merged timeline. The timeline-monad library is not unique in this respect – RxJS has similar capabilities.

To explain more of what a monad is, we really need to cover functors. A functor is some structure whose values can be mapped, leaving the structure itself alone. Examples:

  • Arrays are functors: mapping str => str.length over an array of two elements ['hi', 'sup'] yields another array of two elements [2, 3].
  • Promises are (almost*) functors: mapping str => str.length over a promise for hello yields another promise for 5 (sadly, TC39 named this method .then instead of .map)
  • Trees can be functors: mapping str => str.length over a tree of triangular shape tree('hi', tree('left'), tree('right')) yields another tree of triangular shape tree(2, tree(4), tree(5)).

Timelines are also functors: timelineA.sync(str => str.length) yields another timeline, whose values are published at the same time as timelineA but which are numbers instead of strings. The "timeline" structure is identical, but the values are transformed.

Cool cool, so where's the monad?

Well, a monad is a functor which also allows you to "put a value in the monad" AND (this is the really special bit) lets you fuse nested layers of structure in a "sensible" (law-abiding) way. Examples:

  • Arrays are monads: you can put a single value into an array (v => [v]), and you can flatten nested layers of arrays ([[5], [1, 4], []].flat() yields [5, 1, 4])
  • Promises are (almost*) monads: you can put a single value into a promise (v => Promise.resolve(v)) and you can flatten nested layers of promise (promiseA.then(a => Promise.resolve(a + 1)) yields not a nested promise, but rather just a single-layer promise; put another way, Promise.resolve(Promise.resolve(1)) is equivalent to just Promise.resolve(1)).

Timeline values from this library are also monadic.

// a timeline of timelines? Nope, a *merged* timeline
const threePingsPerClickT = mouseClicksT.sync(_ => createThreePingsT())

If you return a timeline from a timeline.sync callback, the returned timeline from sync is a single-layer aggregation of events "flattened" down into one timeline – the events will be those resulting from the "inner" returned timelines.

*Special note: to be truly functors or monads, structures must obey specific mathematical laws that guarantee their behavior is sensible and dependable. Those laws are out of scope for this post, but unfortunately Promises do not adhere to them, specifically because instead of having separate map and flatten methods, they have a single method then. That means that sometimes, then maps (like a functor) and sometimes, it flattens (like a monad) – meaning it doesn't strictly obey either sets of laws 100% of the time.

nestedsoftware profile image
Nested Software • Edited

I can't really answer your questions, but I took a quick look at timeline-monad ( ).

The idea seems to be very similar to js promises; except js promises can only be resolved or rejected once, whereas this code supports a sequence of events flowing from a single timeline. It would be like having a promise where the then function is called potentially multiple times. That seems to be its chief purpose as compared with promises.

I'm not sure what happens when there are multiple processing steps and each processing step is asynchronous though. As new events keep triggering the sync functions, do they queue up in the subsequent steps to make sure order is preserved? My gut feeling is that this approach is problematic.

jamesmh profile image
James Hickey

Timeline monad uses [functional] reactive programming.

Reactive programming is simply focusing on everything as a stream.

It's kinda like eating a 3-course meal - you have a stream (over time) of meals being given to you which you consume one-at-a-time.

Or, you might want to process your 3-course meal by combining all meals (waiting till they are all given to you) and then consuming them all-at-once.

Streams are basically an array of values that will be filled up as time moves forward. You will have some "observer" that is watching the stream. As values get "pushed" into that array (or stream), you will have some series of functions chained together that will transform and modify each value(s) however needed.

So, for example, you can batch these values easily, combine them, etc.

This is the default in AngularJs apps when trying to build views that reactively render based on changing data.

What's different from pub-sub? Nothing in my mind... you are subscribing to an observable which emits events whenever a new value is available. Your subscribed handler is invoked at that time.