Skip to content

Instantly share code, notes, and snippets.

@balanza
Last active August 2, 2022 01:51
Show Gist options
  • Save balanza/c9c0b7a5c7539eacba9859b1af8f1891 to your computer and use it in GitHub Desktop.
Save balanza/c9c0b7a5c7539eacba9859b1af8f1891 to your computer and use it in GitHub Desktop.
fp-ts didactic examples
type Option<A> = None | Some<A>;
import * as O from "fp-ts/Option";
O.some(42); // { _tag: "Some", value: 42 }
O.none; // // { _tag: "None" }
declare const myVar: object;
O.some(myVar); // { _tag: "Some", value: myVar }
// ^^^^^ può essere null!!
declare const myVar: object;
O.fromNullable(myVar) // Option<object>
// ^^^ può essere sia None che Some<object>,
// a seconda del contenuto della variabile
import * as O from "fp-ts/Option";
import * as E from "fp-ts/Either";
O.fromNullable // crea un Option da un valore
E.fromNullable // crea un Either da un valore
import * as O from "fp-ts/Option";
O.isSome
O.isNone
const maybeFoo = O.some("foo");
maybeFoo.value;
// ^^^^ Build error: value non è definito per Option<string>
if (O.isSome(maybeFoo)) {
console.log(maybeFoo.value); // "foo"
} else {
console.error(maybeFoo.value);
// ^^^^ Build error: value non è definito per None
}
const toEuro = (n: number): string => `€${n}`;
const toMaybeEuro = O.map(toEuro); // "eleva" toEuro per funzionare con Option
toMaybeEuro(O.some(42)); // { _tag: "Some", value: 42 }
toMaybeEuro(O.none); // { _tag: "none" }
import {pipe} from "fp-ts/function";
pipe(
42,
O.some,
O.map(toEuro)
);
const applyDiscount = (perc: number) =>
(n: number): number => n * (1 - perc / 100);
const toRounded = (digits: number) =>
(n: number): number => Math.round(n * 10 ** digits) / 10 ** digits;
const toEuro = (n: number): string => `$${n}`;
pipe(
myPrice,
O.fromNullable,
O.map(applyDiscount(35)),
O.map(toRounded(2)),
O.map(toEuro)
);
type Product = { name: string; price: number; };
const products = new Map<string, Product>();
const getFinalPrice = (productId: string): O.Option<string> =>
pipe(
productId,
O.fromPredicate(s => s.length > 0), // Un altro smart constructor!
O.chain(id => {
const product = products.get(id);
return O.fromNullable(product;
// equivalente di (product ? O.some(product) : O.none);
}),
O.map(product => product.price),
O.map(applyDiscount(35)),
O.map(toRounded(2)),
O.map(toEuro)
);
pipe(
productId,
getFinalPrice,
O.fold(
() => "Cannot find product, sorry :(",
price => `You will pay ${price}`
)
)
type Either<L, R> = Left<L> | Right<R>
import * as E from "fp-ts/Either";
E.right(42);
E.left("not 42");
const validatePrice = (price: number): E.Either<string, number> =>
price >= 0
? E.right(price)
: E.left("price cannot be negative")
// a partire da un'istanza di Option
pipe(
42,
O.some,
E.fromOption(() => "cannot handle null-ish values")
)
// o da operazioni che possono fallire sollevando un'eccezione
E.tryCatch(
() => JSON.parse('{"baz":true}'),
exception => new Error()
);
const checkMinPrice = (price: number): E.Either<string, number> =>
price >= 10 // arbitrary threshold, just an example
? E.right(price)
: E.left("price cannot be less than 10");
pipe(
price,
validatePrice,
E.map(applyDiscount(23)),
E.chain(checkMinPrice),
E.fold(
reason => new Error(reason),
toEuro
)
);
pipe(
price,
validatePrice,
E.mapLeft(failure => new Error(`Validation error: ${failure}`))
E.map(applyDiscount)
);
pipe(
1, // checkMinPrice fallirà
validatePrice,
E.mapLeft(failure => new Error(failure)),
E.chain(checkMinPrice),
E.mapLeft(failure => {
// ^^^ Error | string
// failure può arrivare sia da validatePrice che da checkMinPrice
})
);
pipe(
1,
validatePrice,
E.mapLeft(failure => new Error(failure)),
E.chain(price =>
pipe(
price,
checkMinPrice,
E.mapLeft(failure => {
// ^^^ failure può arrivare SOLO da checkMinPrice
})
)
)
);
const fooOrError = E.right("foo");
if (E.isRight(fooOrError)) {
console.log(fooOrError.right);
console.log(fooOrError.left);
// ^^^^ Build error: left non è definito per Right
} else {
console.log(fooOrError.left);
console.log(fooOrError.right);
// ^^^^ Build error: right è definito per Left
}
type Task<T> = () => Promise<T>
type TaskEither<L, R> = () => Promise<Either<L, R>>
import * as TE from "fp-ts/TaskEither";
const processPayment = async (price: number) => {
return processPaymentOnProvider();
}
const procedure = pipe(
myPrice,
validatePrice,
TE.fromEither,
TE.chain(actualPrice =>
TE.tryCatch(
() => processPayment(actualPrice),
err => "è successo qualcosa durante il pagamento"
)
),
TE.map(_ => "OK"),
TE.toUnion // una utility, di fatto una fold
);
procedure().then(result => {
console.log(result)
});
Data Type Si usa per
Option Un valore che c'è o è null-ish
Either Validazione o operazione che può fallire
TaskEither Operazione asincrona che può fallire
Operazione Si usa per
type guard type narrowing di un Data-Type in un sotto-tipo
smart constructor Costruire un Data Type a partire da un valore o da un altro Data Type
map Applicare una trasformazione al valore contenuto senza cambiare il sotto-tipo
chain Applicare una trasformazione al valore contenuto cambiando il sotto-tipo
fold Far convergere i due rami della computazione
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment