Friday, December 12, 2025

Functional programming in Javascript (4)

Please take a look at other posts about functional programming in JavaScript:

  1. Part 1 - what functional programming is about
  2. Part 2 - functional pipelines
  3. Part 3 - the Y combinator
  4. Part 4 - monads

Monad is one of the most interesting patterns in functional programming. In short, it allows to operate on values that have an extra information attached.

There are various monads and technically, a specific monad is conceptually similar to a class (from object languages). Instances of the monad wrap values, a monad instance contains the wrapped value and an extra state.

A generic monad definition says that you need:

  • a type constructor – that creates a monad type M from some value type A. JavaScript doesn't really care as there are no types at compile time.
  • a return function – that packs/wraps a value into the monad: a → (M a)
  • a bind function – that takes a monad instance, a monadic operation on a pure value and executes this operation and returns a new monad instance: (M a) → (a -> M b) → (M b)

In some programming languages, monads have their own syntactic sugar. For example, in Haskell, the bind is represented by >>= operator.

Our JavaScript example is the Maybe monad. Instead of just saying what problem it solves, let's see the problem.

Let's take a function that takes a string. We want to parse it to convert it to a number and reverse the number. There are two possible points of failure: the string can possibly not represent a number (parsing fails) and the number can possibly be 0 (reversing fails). In a typical imperative/object language, there are two patterns of handling such failures:

  • nested ifs
  • exception handing

The first approach is the least elegant, each possible point of failure requires an extra condition which causes the code to form a pyramid:

function process(input) {

    const value = Number(input);

    if ( !isNaN(value ) ) {
        if ( value != 0 ) {
            const reciprocal = 1 / value;
            return reciprocal;
        }
    }

    return null;
}

console.log( process("not-a-number") );
console.log( process("0") );
console.log( process("2") );

The other approach - a global exception handler - is much cleaner as each possible failure is caught by the very same exception handler:

function process(input) {
    try {
        const value = Number(input);
        const reciprocal = 1 / value;

        return reciprocal;
    }
    catch {
        return null;
    }
}

console.log( process("not-a-number") );
console.log( process("0") );
console.log( process("2") );

The Maybe Monad is a functional alternative to such global exception handling section. At each possible failure point we will have a Maybe instance that will either contain a non-empty value or an empty value and checking the non-emptiness will either be performed in each consecutive step or inside the bind (which will skip a step in case of empty value).

If you come from imperative/object languages that support Nullable wrappers over values (yes, C#, it's about you!) then yes, Maybe instance is really similar to Nullable instance. What is the extra feature here is bind that will allow us to avoid the Pyramid of Doom.

But, we'll have to put some effort into that first.

Let's start with Monad class:

class Maybe {
  constructor(value) {
    this.value = value;
  }

  bind(f) {
    if (this.value === null || this.value === undefined) {
      return Maybe.Nothing();
    }
    return f(this.value);
  }

  static of(value) {
    return new Maybe(value);
  }

  static Nothing() {
    return new Maybe(null);
  }
}

Note how simple it is. The bind is here, the return is here, too (it's called of) and we even have an extra constructor for the empty value (Nothing).

We are ready to implement the two monadic operations. Go back to the very start and consult the signature of bind - monadic operations are functions that take pure values and return monad instances:

// parsing
function parse(str) {
  const n = Number(str);
  return isNaN(n) ? Maybe.Nothing() : Maybe.of(n);
}

// reversing
function recp(n) {
  return n === 0 ? Maybe.Nothing() : Maybe.of(1 / n);
}

And ... that's it!

Oh, really?

Yes! We can already write monadic code:

function process1( i ) {
  return Maybe.of(i) // wrap input
    .bind(input => 
      parse(input) // parse
        .bind(number => 
          recp(number) // reverse
            .bind(reciprocal => 
              Maybe.of(reciprocal) // wrap output
            )
        )
    );
}

console.log( process1( null ) );
console.log( process1( "0" ) );
console.log( process1( "2" ) );

Note that there's no single if in the code because ifs are hidden inside monadic operations and inside bind!

But wait! It's disappointing! The Pyramid of Doom is still there!

How other functional languages tackle this? Take a look at Haskell, a raw bind has the same problem, you need to pass an arrow function as an argument to bind and each consecutive monadic operation is a new arrow function next to the previous one:

  getLine >>= \name -> putStrLn ("Hello, " ++ name ++ "!")

The first possible step into right direction would be - since bind returns a monad instance - to flatten the code:

function process1(i) {
  return Maybe.of(i) // wrap input
    .bind(input =>
      parse(input))  // parse
    .bind(number =>
      recp(number))  // reverse 
    .bind(reciprocal =>
      Maybe.of(reciprocal) // wrap output
    );
}

That looks much better, the pyramid is gone. But it has a serious drawback. In the previous approach, each intermediate result (input, number, reciprocal) was caught by a closure and was available in the very next nested arrow function. All three are available at the end.

In this new, flatten version, this feature is gone. No closures, each bind is side-by-side with other binds and has only its own value. To be able to pass all intermediate values down the pipeline, we'd have to wrap them in arrays and return monadic instances that wrap two, three raw values. That doesn't sound right.

How Haskell solves this? They have the syntactic sugar called the do notation which is basically a special block of code where monadic instances can be unwrapped into temporary variables using the <- operator.

main = do
  name <- getLine
  putStrLn ("Hello, " ++ name ++ "!")

And yes, this is the ultimate syntax. The do notation is the crucial element of the language that makes monads feel natural in the language. It's so important that other functional languages have it, OCaml has the let*, Scala has for { } blocks!

Can we have it in JavaScript? Well, we can't add our own syntactic sugar into the language. But we can have a function!

Let's face it. It's not obvious. Monadic operations return monad instances and it's bind that unwraps monad instances into raw values. Such do-like function should allow us to unpack monads and also, keep all unpacked values in a single closure-like "scope" so that all of them are available after obtained.

One of the very beautiful ways to do this involve using JavaScript's generators! We will write the do-block as a generator function and we will unpack monad instances using yield! JavaScript is unique - yield is a two way operator. It not only returns a value to the caller but also lets the caller to push a value back to the generator so that yield can be used at the right side of the assignment operator! No other language that has yield support this two-way communication!

JavaScript's do is surprisingly short then but very clever. We call it doM as do is a reserved keywork in the language:

function doM(gen) {
  function step(value) {
    const result = gen.next(value);
    if (result.done) return result.value;
    return result.value.bind(step);
  }
  return step();
}

A side note: this function uses this highly unique feature of JavaScript that allows us to call gen.next(value) to push values into the generator. You can study this contrived example:

// generator returns 1,2,3 to the client
function* gen() {
    var a = yield 1;
    console.log( `a = ${a}` );
    var b = yield 2;
    console.log( `b = ${b}` );
    var c = yield 3;
    console.log( `c = ${c}` );
}

var it = gen();
var i = 0;
var a = [null, 17, 18, 19];
// the client pushes 17, 18, 19 back into the generator
while (true) {
  const { value, done } = it.next(a[i++]);
  if (done) break;
  console.log(value);
}

Back from the side note, with doM monadic code can be refactored to clean, linear code where there's no bind at all and monadic operations appear on the right sides of the yield operator but they yield raw values on left sides of assignments. And since a generator is a function, all raw values are available in it after they are assigned!

function process2( i ) {
    return doM(
        function* () {
            const input      = yield Maybe.of(i); 
            const number     = yield parse(input);
            const reciprocal = yield recp(number);

            return Maybe.of(reciprocal);
        }()
    );
};

console.log( process2( null ) );
console.log( process2( "0" ) );
console.log( process2( "2" ) );

Holly molly, that's nice! There's no try-catch but it feels like one, whenever an operation fails, a monad with empty value is passed down the pipeline and bind just skips over it!

There's one almost-monad built into JavaScript - it's the Promise monad, the one that allows async operations. It has its own syntax, no do block and no generator with yield but there's async as a function decorator and await for unpacking raw values.

async function process3( i ) {
   const input      = await Promise.resolve(i); 
   const number     = await parseAsync(input);
   const reciprocal = await recpAsync(number);
   
   return Promise.resolve(reciprocal);
};

Note that async-await is not about exception handling, it's about handling async operations. If you just thought that there could be other interesting monads that solve other issues but also benefit from this kind of do-notation syntax then yes, functional languages like OCaml and Scala have other monads and Haskell features so called monad transformers where you combine different monads in one do block!

Monad Promise
return / of Promise.resolve
bind then
do function async decorator
yield inside the do block await in an async function

What we just demonstrated, though, is that we can have similar feature, built in a functional way. And yes, async/await is internally implemented very similarily to yield generators. Before async/await syntactic sugar was introduced, people were coding their own async monads using the pattern we've just demonstrated, using yield and an auxiliary do-like function. These early attempts can still be found, take a look at the co library for example.

Happy coding!

No comments: