Un Either rappresenta una computazione che può avere due risultati, chiamati rami o binari (left e right).

La maggior parte delle volte, lo si usa per rappresentare una computazione che può fallire. Il ramo sinistro rappresenta il caso d’errore, il ramo destro il caso di successo.

Quando si manipola un Either, se entriamo nel ramo sinistro, la maggior parte delle volte non eseguiamo rielaborazioni dell’errore e restituiamo l’errore incapsulato da esso (è sempre accessibile, però con altre funzioni di trasformazione). Se siamo nel ramo destro continuiamo a manipolare il valore mantenendolo nel ramo destro.

Un Either è tipizzato Either<E, A> dove E è il tipo del ramo sinistro (E sta per errore per convenzione) ed A è il ramo destro.

Si preferisce utilizzare un Either rispetto ad una Option quando lo stato di errore è significativo e va mantenuto (esempio: validazione).

Costruire un Either

Partendo da un valore

Il modo più semplice per costruire un Either è utilizzare i costruttori left e right che restituiscono un valore incapsulato come un Either di tipo Left o Right.

import * as E from 'fp-ts/Either';

const leftValue = E.left("value"); // -> questo è il ramo sinistro
const rightValue = E.right("value"); // -> questo è il ramo destro

Si ha a disposizione anche la funzione fromNullable. Nel caso in cui il valore fosse null o undefined bisogna fornire a fromNullable cosa va inserito nel ramo sinistro, ed il valore viene automaticamente inserito nel ramo destro.

import * as E from 'fp-ts/Either';

const leftValue = either.fromNullable("value was nullish")(null); // Either.Left('value was nullish')
const rightValue = either.fromNullable("value was nullish")("value"); // Either.Right("value")

È possibile fornire una funzione di validazione (predicate) per costruire un Either con la funzione fromPredicate.

Rispetto ad Option è un po’ più complesso.

È necessario passare una funzione che controlli che il valore sia corretto (per decidere in quale ramo andare). Questa funzione può essere una semplice funzione che restituisce un boolean, oppure una type guard. Successivamente bisogna fornire un valore per il ramo sinistro.

import * as E from 'fp-ts/Either';

type EvenNumber = number;
const isEven = (num: number) => num % 2 === 0;
const isEvenTypeGuard = (num: number): num is EvenNumber => num % 2 === 0;
const eitherBuilder = E.fromPredicate(
  isEven, // qui possiamo utilizzare isEvenTypeGuard per fare inferenza del tipo number ed avere un Either<E, EvenNumber>
  (number) => `${number} is an odd number`
);
// Qui quando utilizziamo eitherBuilder si ottiene un Either<string, number>

const leftValue = eitherBuilder(3); // Either.left('3 is an odd number')
const rightValue = eitherBuilder(4); // Either.right(4)

Da un’altra struttura dati

Si può costruire un Either partendo da una Option. Funziona esattamente come fromNullable, come abbiamo visto prima una Option può rappresentare un valore nullable.

import * as E from 'fp-ts/Either';
import * as O from 'fp-ts/Option';

const noneValue = O.none;
const someValue = O.some("value");

const left = E.fromOption("value was nullish")(noneValue); // Either.Left('value was nullish')
const right = E.fromOption("value was nullish")(someValue); // Either.Right('value')

Lavorare con i valori

Quando un valore è incapsulato da un Either, proprio come Option, si può trasformare ed eseguire computazioni sequenziali.