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 aLeft
in the pipeline there are no more operations possible and processing will stop. The value inLeft
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.
🦉 Theidentity
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
Comments
Post a Comment