JS Journey into outer space - Day 2

This article was written as part of a knowledge sharing program I created for front-end developers. It builds up to creating reusable abstractions by studying async transducers.

Day 2

That's all good and nice, but wait just one moment! I'm already using RxJS (or Lodash or your framework here) and it already provides composable operator functions!

You Ain't Gonna Need It?

You would be right of course, and getting invested in yet another new framework for that extra bit of performance might not be worth it. However, do you need separate frameworks if there would be a single one to do all you need? Does it even exist? At first glance transducers-js looks great, but the first thing you might notice is that it hasn't been updated recently and doesn't use TypeScript. And of course we want typed functions.

A nice typed functional framework is fp-ts. It's also quite complex, due to its abstract nature (if not its scarce documentation). Let's see if we can familiarize ourselves with it by implementing transducers for Either.

Right is right but Left is wrong

Either is a union type that indicates either failure or success in the most general sense. When successfull the type is an instance of Right containing an arbitrary value. In case there was a failure for whatever reason it's an instance of Left.

🦉 Either can be used to shortcut operation in an operation pipeline: as soon as there is a Left in the pipeline there are no more operations possible and processing will stop. The value in Left may contain useful information about what went wrong. In JavaScript it may represent a runtime error, but that's an imperative construct and has no equivalent in math or logic.

We need to come up with similar procedures we created for the array version. The generator needs to yield the value in Right. There's a function fold we can use that is like reduce. It takes two arguments, a function that operates on the Left value and one that operates on the Right. Since we just want to get the value unmodified we can use identity, which simply returns whatever was passed in. We don't care about the Left value here since it will never be operated on.

🦉 The identity function is at the bases of functional programming as it expresses a relationship between things mathematically. It's used to prove several laws in e.g. logic and set theory.

function* eitherGenerator<E, A>(input: Either<E, A>) {
  yield fold(identity, identity)(input);

The concat function might look a bit more tricky, but we only need to replace the current Right with the updated value. To achieve this we can use map to substitute the Right. We just ignore the original value, since it will be operated on in the main pipeline.

By this time you may realise this is a bit of a silly and contrived example. Oh well, as long as it helps to understand both Either and transduce better, right? Left 🤓

const eitherConcat = <E, A>(a: Either<E, A>, c: A) => map(() => c)(a);

No beginning no end

Something tricky comes up when dealing with the initial Either to pass in. We only need to operate on Right, but when Left is passed as initial value and the input is Right we always end up with Left. However, if the initial value is Right and the input is Left then the initial value is never updated and we end up with Right, which is wrong. Hmm.

And what if we don't pass an initial value? Then Right is taken as the initial value and is never operated on. Hmm hmm. Fixed by passing the input as the initial value. Nice.

Finally let's appreciate just how silly this exercise is.

const prependHello = (a: string) => `hello ${a}`;
const isWorld = (a: string) => a === 'world';
const hello = compose(filter(isWorld), map(prependHello));
const xform = hello(eitherConcat);
const input = right('world');
const result = transduce(input, xform, eitherGenerator, input);
console.log(isRight(result)); // true
console.log(toUnion(result)); // hello world


Popular posts from this blog

The Bling Chain

Towards a safer JavaScript

Forms as state containers Part 4: form generation