DEV Community

Getting started with fp-ts: Either vs Validation

Giulio Canti on April 03, 2019

The problem Say you must implement a web form to signup for an account. The form contains two field: username and password and the follo...
Collapse
 
grancalavera profile image
Leon Coto • Edited

Hi Giulio, thanks for the great library and the great documentation. I'm following along, and I'd like to know how would you implement a function that takes a list of Validators and applies them to a single input, something along these lines:

import { Either } from "fp-ts/lib/Either";
import { NonEmptyArray } from "fp-ts/lib/NonEmptyArray";

type Validation<E, A> = Either<NonEmptyArray<E>, A>;
type Validator<E, A> = (a: A) => Validation<E, A>;

declare function validations<E, A>(vs: Validator<E, A>[], a: A): Validation<E, A>;
Enter fullscreen mode Exit fullscreen mode

Notice that for simplicity I'm making the Validation type take a NonEmptyArray of errors, since I'm mainly interested on how to combine the validations from a dynamically constructed array.

Finally, if the list of validations is empty, we can assume the input is valid.

Thanks!

Collapse
 
gcanti profile image
Giulio Canti

This signature is weird

type Validator<E, A> = (a: A) => Validation<E, A>;

If you already know that the input has type A, why are you validating in the first place?

Check this out: Parse, don't validate

Collapse
 
grancalavera profile image
Leon Coto

Ah, yes. That's a very good observation thanks. Most likely the Validator would be producing some "valid" version of A. Point taken.

Now, if we come back to the original email address validation example, how would you combine a dynamically built list of email validation functions? Suppose we load the validations to the client from a service, that gives us the business rules we want to validate?

The current example takes a NonEmptyArray as imput, and I have not been able to find a way to combine validations from an Array.

Again, many thanks, and thanks for your suggestion.

Thread Thread
 
rossh87 profile image
Ross Hunter • Edited

Hi Leon, I'm a little late to the party, but here's a gist of an implementation that I think does what you want. For this example, we assume our application requires a string of type Name that satisfies 2 business rules: to be considered a valid Name, a candidate string must be at least 5 characters long, and it must contain the letter 't'. The types could be improved, and the function should pry be generalized, but... you get the idea at least.

Edit: For whatever reason I can't seem to embed a gist right now, so let's try a CodeSandbox.

Thread Thread
 
grancalavera profile image
Leon Coto

Thanks Ross, this seems to be just what I was trying to achieve. Much appreciated.

Collapse
 
isthatcentered profile image
Edouard Penin

Thank you so much for those articles, Fp-ts has done a lot to increase my speed everyday at work!

I'm struggling to figure what this would look like without the sequenceT helper. Would you have time to provide a snippet ?

(I tried going through the sequenceT source but I can't quite figure out what's going on yet)

Collapse
 
gcanti profile image
Giulio Canti

This is what sequenceT is doing under the hood (when specialized to getValidation(getSemigroup<string>()) + three validations)

function specializedSequenceT(
  firstValidation: Either<NonEmptyArray<string>, string>,
  secondValidation: Either<NonEmptyArray<string>, string>,
  thirdValidation: Either<NonEmptyArray<string>, string>
): Either<NonEmptyArray<string>, [string, string, string]> {
  // Applicative instance for `Either<NonEmptyArray<string>, A>`
  const V = getValidation(getSemigroup<string>())

  // builds a tuple from three strings
  const tuple = (a: string) => (b: string) => (c: string): [string, string, string] => [a, b, c]

  // manual lifting, check out the "Lifting" section in "Getting started with fp-ts: Applicative"
  return V.ap(V.ap(V.map(firstValidation, tuple), secondValidation), thirdValidation)
}

function validatePassword(s: string): Either<NonEmptyArray<string>, string> {
  return pipe(
    specializedSequenceT(minLengthV(s), oneCapitalV(s), oneNumberV(s)),
    map(() => s)
  )
}
Collapse
 
isthatcentered profile image
Edouard Penin

Ohhhh, okay, the tuple of results is what you apply to. Thank you so much for your answer!
(And yes, going back to the applicative article right now 😉)

Collapse
 
patroza profile image
Patrick Roza

How would this look in 2.0.0-rc.7?
For starters getArraySemigroup and getApplicative seems to be missing :)

Collapse
 
gcanti profile image
Giulio Canti

With fp-ts@2 will be something like

import { sequenceT } from 'fp-ts/lib/Apply'
import { Either, getValidation, left, map, mapLeft, right } from 'fp-ts/lib/Either'
import { getSemigroup, NonEmptyArray } from 'fp-ts/lib/NonEmptyArray'
import { pipe } from 'fp-ts/lib/pipeable'

const minLength = (s: string): Either<string, string> =>
  s.length >= 6 ? right(s) : left('at least 6 characters')

const oneCapital = (s: string): Either<string, string> =>
  /[A-Z]/g.test(s) ? right(s) : left('at least one capital letter')

const oneNumber = (s: string): Either<string, string> =>
  /[0-9]/g.test(s) ? right(s) : left('at least one number')

function lift<L, A>(
  check: (a: A) => Either<L, A>
): (a: A) => Either<NonEmptyArray<L>, A> {
  return a =>
    pipe(
      check(a),
      mapLeft(a => [a])
    )
}

function validatePassword(s: string): Either<NonEmptyArray<string>, string> {
  return pipe(
    sequenceT(getValidation(getSemigroup<string>()))(
      lift(minLength)(s),
      lift(oneCapital)(s),
      lift(oneNumber)(s)
    ),
    map(() => s)
  )
}

console.log(validatePassword('ab'))
/*
=> left([ 'at least 6 characters',
    'at least one capital letter',
    'at least one number' ])
*/
Collapse
 
josephr5000 profile image
josephr5000

I've been following your blog series, and am keen to better understand how this applicative validation works so I pasted your sample above into my code. Unfortunately I get "Argument of type 'Either, [string, string, string]>' is not assignable to parameter of type 'unknown[]'." error on the sequenceT(getValidation...line above. Using fp-ts @ 2.2.0 and Typescript @ 3.7.3. What would be expecting a parameter of type unknown[]?

Thread Thread
 
gcanti profile image
Giulio Canti

Sorry, I can't repro, the sample above looks fine

Collapse
 
pradeepdas profile image
pradeepdas

how to extract the left validation messages or right person object from the validatePerson method

Collapse
 
gcanti profile image
Giulio Canti

You can fold the result

Collapse
 
ashiltendonu profile image
Aşil Tendonu

Thats gr8 !
function lift(check: (a: A) => Either): (a: A) => Either, A> {