Evojam

View Original

Practical Intro to Monads in JavaScript: Validation

Few weeks ago I published a practical Intro to Monads in JavaScript where I covered basics of Identity and Maybe monads. Some time later I added a tutorial on Either monad and fails-fast error handling. This time I will show you error accumulation in a simple Validation use case.

Well, it may be all right in practice, but it will never work in theory.

Warren Buffett


All examples are based on monet.js - a tool bag that assists Functional Programming by providing a rich set of Monads and other useful functions. I'll use arrow functions introduced in ES6. They are much more readable than regular functions when used as single operation "callbacks". Also in most examples TypeScript type definitions will be added to enhance overall readability.

Either Validation success or Validation fail

Do you remember the Either monad? The one that could have been either Right(value) or Left(error)? The Validation also has two sides. It can hold Success(value) or Fail(error). As monet.js Validation docs state:

Validation is not quite a monad as it doesn’t quite follow the monad rules, even though it has the monad methods.

Validation is similar to a Monad, and the difference is academic. Since we're only interested in practice, not theory, let’s make Validation the next step in our monadic adventures in JavaScript.

Validation in practice

Validation - this name is quite straightforward, isn't it? Probably we should validate something! As we already know from intro of this article, it's capable of accumulating errors (failures). It's for sure a good tool to validate a collection of things that may succeed or fail to meet some rules. Or one item that has to be validated against many rules. The HTML <input /> element would be a great testing ground!

Let's say we have some validator, from a 3rd party library, that takes an input value and a validation type label and it returns true / false:

See this content in the original post

Now we can wrap it to work with Validation instead of boolean and add some currying:

See this content in the original post

Now we have our implementation of a validator which returns a Validation that can hold a string with info about the failure or a successfully validated value of input. Let's make it work together:

See this content in the original post

Something is wrong here, isn't it? We achieved fast-fail - if any isValid() call returned fail, we go straight to end of chain with a failed Validation. That is not what we wanted - we do not know if any other validation has failed.

Error accumulation

Fortunately monet.js Validation has an .ap() method which implements the applicative functor pattern and provides a way to accumulate errors:

See this content in the original post

So what? So, we can collect our error information! The .ap() method accepts a Validation of a function and:

  • if both validations are successful it binds this function to held value and wraps returned value (just like .map()) in a successful Validation;

  • if one of these validations is failure it just returns the failed Validation;

  • if both validations are failures it returns a Validation holding accumulated errors.

We'll need some kind of value accumulator. Actually there is one provided by monet.js - the [.acc() method] on a Validation. It returns a successful Validation holding a magic function - a function returning itself. For ever.

See this content in the original post

Then we can:

See this content in the original post

The validated would reference either a successful Validation holding that magic accumulator function or a failed one with accumulated errors inside. We will just need to .map() successful output to a Validation holding the input.value:

See this content in the original post

We get a success with input value or… concatenated strings, e.g. 'LEGAL_DOMAINSREQUIRED'. Not so good…

Clean it up

Implementation of a Validation .ap() method accumulates failure information using Semigroup concatenation. If it gets a string, it just adds previous one to current. Otherwise it checks for .concat() method on passed failure. If no .concat() method is available it throws. It seems that we have to wrap our possible failures in an array, right?

See this content in the original post

What a spaghetti… To make it a bit more cleaner let's move some parts to the inside of isValid and switch the args order:

See this content in the original post

Then we can:

See this content in the original post

And we get a success with input value or an array of strings representing failed validations. It has only one problem - we'll have to nest ap in ap in apn times, where n is number of validations made on single value. It would be much better to put this computation into a loop:

See this content in the original post

The example above does exactly the same as the one before, but it can accept any number of validation rules. The only downside is the Success().acc() passed as accumulator to the .reduce(). And we can actually move it inside .reduce() loop, because if no acc is passed, it'll take first two items on first run (instead of accumulator and first item). This way:

See this content in the original post

Now we can just wrap it in a function now…

Finally

…so here is a final, reusable tool:

See this content in the original post

Is that all?

No :)

This was an example of error accumulation for testing a single value. There is a different pattern for accumulating many values - e.g. when validating a whole form. But to achieve this we'll have to use an immutable List which I'll introduce in next article.

Further reading