paint-brush
Using Generators as syntax sugar for side effectsby@vitaliy.akimov
1,381 reads
1,381 reads

Using Generators as syntax sugar for side effects

by Vitaliy AkimovJune 11th, 2018
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

This story shows Generators as an explicit but seamless syntax for programs with asynchronous operations, shared mutable state and other side effects. The conversion is based on so-called Monads.

Coins Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Using Generators as syntax sugar for side effects
Vitaliy Akimov HackerNoon profile picture

This story shows Generators as an explicit but seamless syntax for programs with asynchronous operations, shared mutable state and other side effects. The conversion is based on so-called Monads.

In spite of the scary name, Monads are a very simple concept. You already use them when you change some variable value or output anything or throw/catch an exception. Monads first appeared in Computer Science to support mathematical reasoning about side-effects in imperative languages such as JavaScript.

Other researchers were designing practical languages with only pure functions. Using only pure functions makes programs more verbose and harder to read. Monads were applied as a practical tool for converting programs with effects into pure ones. Here is a citation from one of the best monads tutorial — Monads for functional programming by Philip Wadler (1995):

It is with regard to modularity that explicit data flow becomes both a blessing and a curse. On the one hand, it is the ultimate in modularity. All data in and all data out are rendered manifest and accessible, providing a maximum of flexibility. On the other hand, it is the nadir of modularity. The essence of an algorithm can become buried under the plumbing required to carry data from its point of creation to its point of use.

Doesn’t it sound familiar? E.g., property drilling for React Components and one of the reasons for state management to solve it.

Original abstract do-notation is a macro converting programs looking like imperative one into abstract API calls. The concrete implementations of that APIs can construct an Iterable object, or a Promise or many other things. This way the same syntax (and the same programs) can be reused in different contexts.

JavaScript has syntax extensions similar to do-notation. They are async and/or generator functions. Unlike original do-notation they are converted to concrete API calls ( Promise#then, and Iterable constructors) by JavaScript compiler or some transpiler (e.g. regenerator). These concrete functions are almost instances of abstract monad API.

Async and generator functions are based on Coroutine monad. It can be converted into many other monads, but not all. There is a well-known fact in JavaScript community — generator functions can replace async functions. Asynchronous functions written using generators syntax can be canceled, unlike standard async functions. The only overhead is the need to call a wrapper function, which converts Iterable into a Promise.

There are many examples of using generators for async code so I’ll illustrate the idea on another case instead. It is maybe not so practical, but we can turn a generator function with a mutable state into a pure function.

Here both functions incr and incrX2 have side effects. They change and read shared data. But the resulting function state(incrX2)is pure. The function state does actual conversion between Iterable into a State monad.

This is how it looks with inlined framework functions:

The example skips abstract API layer. There are quite a few options to chose its basis, but the most simple one is two functions: of and chain. They both return monadic(effectful) value. It is an abstract thing and can be anything depending on the concrete API implementation. For the abstract interface, the values of this type are entirely opaque.

  • of — takes any value and returns effectful value, what it does exactly with the argument is defined by interface implementation
  • chain — takes some effectful value and a function mapping anything to another effectful value, returns some other effectful value

The concrete implementations of the function can do anything if this conforms to so-called monadic laws. In fact, my choice of chainname is typical for JavaScript libraries but a bit misleading. It suggests some concrete implementation, chaining of something. But, again, it is some abstract thing, and the only requirement is monad laws conformance.

Here are the laws:

  • (f, x) => chain(of(x), f) should be equal to (f, x) => f(x)
  • m => chain(m, of) should be equal to m
  • (m, f, g) => chain(chain(m, f), g) should be equal to (m, f, g) => chain(m, x => chain(f(x), g))

If the laws hold we can use the API in do-notation, or in some abstract functions working for any monad.

Let’s convert Iterable into this abstract interface. It is almost the same like for State except the functions are replaced with abstracted calls.

For State, effectful value is a function taking some original state and returning a pair of a resulting value and a new state value. Here is State monad definition using the abstract intermediate layer:

And Promises:

The first monad law doesn’t work for Promises, it is fine for practical purposes though.

Considering generators are enough for Promises and Iterators one may wonder why we need Async Generators. Indeed it is pretty easy to convert monad combining the two effects from plain generators. However, there won’t be any replacement for await-of statement. It is yet another syntax sugar for traversal.

Coroutines/Iterables cannot define be converted to any monad. For example, Observable is a monad, but generators cannot be used as a do-notation for them.

Another useful example is non-determinism. I.e. a computation returning several values. This can be used for embedding logical programming into JavaScript. It is simple to make an implementation for the abstract interface:

Generators syntax won’t work because fun may be called more than once.

There are libraries defining abstract APIs with a few concrete implementations in JavaScript, e.g. fantasy-land. There are also a few do notation as generators implementations trying to simulate multiple resumes by restarting and replaying iterators for the beginning example - burrido. The approach is not safe and not efficient.

I now work on a babel plugin based implementation for do-syntax working for any Monad — EffectfulJS. It offers many options, like persistent state, concrete implementation inlining, implicit parallelization. This may significantly simplify JavaScript code. I’m going to write more about it soon, stay tuned!