Skip to content

Instantly share code, notes, and snippets.

@gillchristian
Last active January 20, 2022 14:46
Show Gist options
  • Save gillchristian/a1a5a60b14d46e7a54cf41f9eea3e0f6 to your computer and use it in GitHub Desktop.
Save gillchristian/a1a5a60b14d46e7a54cf41f9eea3e0f6 to your computer and use it in GitHub Desktop.
import * as Option from "fp-ts/Option";
import * as Either from "fp-ts/Either";
import * as ArrayFP from "fp-ts/Array";
import * as TaskEither from "fp-ts/TaskEither";
import { Ord, contramap } from "fp-ts/Ord";
import { Ord as ordString, Semigroup as semigroupString } from "fp-ts/string";
import { sequenceS } from "fp-ts/Apply";
import { flow, pipe } from "fp-ts/function";
// Functor
const a = [1, 2, 3];
const toString = (x: number) => x.toString();
toString(1);
const len = (s: string) => s.length;
a.map(toString);
// Array.map : (f: (a: A) => B) => Array<A> => Array<B>
// Option.map : (f: (a: A) => B) => Option<A> => Option<B>
pipe(Option.some(1), Option.map(toString));
Promise.resolve(42).then(toString);
// composition
// Array<number> => Array<string> => Array<number>
a.map(toString).map(len) === a.map((x) => len(toString(x)));
a.map(toString).map(len) === a.map((x) => pipe(x, toString, len));
// a.map(toString).map(len) === a.map((x) => x |> toString |> len);
a.map(toString).map(len) === a.map((x) => flow(toString, len)(x));
a.map(toString).map(len) === a.map(flow(toString, len));
const z = flow(toString, len);
// vs.
const z_ = (x: number) => pipe(x, toString, len);
a.map(toString).map(len) === a.map(z);
// Apply / Applicative
// Option<A> = None | Some<A>
//
// Either<L, R> = Left<L> | Right<R>
// Result<E, A> = Err<E> | Ok<A>
const f = (s: string) => (n: number) => `${s} ${n}`;
const optionA = Option.some(42);
const optionB = Option.some("foo");
f("foo")(42); // "foo 42"
pipe(
optionB,
Option.map(f) // Option<(n: number) => string>
);
pipe(
sequenceS(Option.Apply)({ a: optionA, b: optionB }), // Option<{ a: number, b: string }>
Option.map(({ a, b }) => f(b)(a)) // Option<string>
);
const optionC: Option.Option<number> = Option.none;
pipe(
sequenceS(Option.Apply)({ a: optionC, b: optionB }), // Option<{ a: number, b: string }>
Option.map(({ a, b }) => f(b)(a)) // Option<string>
);
// Monad
Promise.resolve(42).then((x) => Promise.resolve(toString(x))); // is not -> Promise<Promise<string>>
// is actually -> Promise<string>
const lenIs5 = (s: string): Option.Option<string> =>
s.length === 5 ? Option.some(s) : Option.none;
pipe(
Option.some("hola"),
Option.map(lenIs5), // Option<Option<string>>
(v) => v
);
// chain : ((a: A) => Option<B>) => Option<A> => Option<B>
pipe(
Option.some("hola"),
Option.chain(lenIs5), // Option<string>
(v) => v
);
pipe(
[1, 2, 3],
ArrayFP.map((x) => [x, 1, 2]), // Array<Array<number>>
// [[1, 1, 2], [2, 1, 2], [3, 1, 2]]
(v) => v
);
pipe(
[1, 2, 3],
ArrayFP.chain((x) => [x, 1, 2]), // Array<number>
// [1, 1, 2, 2, 1, 2, 3, 1, 2]
(v) => v
);
const filter =
<A>(f: (a: A) => boolean) =>
(xs: A[]): A[] =>
pipe(
xs,
ArrayFP.chain((x) => (f(x) ? [x] : []))
);
pipe(
[1, 2, 3],
filter((x) => x > 2) // [3]
);
pipe(
Option.some(4),
Option.chain((x) => (x < 2 ? Option.some(x) : Option.none)), // Option<number>
Option.map(toString) // Option<number>
// Option.none
);
// When to use Option? -> null-able values or error handling
type PoorMansOption<A> = A | null; // cannot implement the interfaces (Functor, etc) laws
const x: PoorMansOption<number> = null;
const y: PoorMansOption<PoorMansOption<number>> = 123; // ???
// Ord contramap
interface User {
name: string;
}
const ordUser: Ord<User> = contramap((user: User) => user.name)(ordString);
pipe(
[{ name: "Zoe" }, { name: "Jane" }, { name: "Mikel" }],
ArrayFP.sort(ordUser)
);
// Types don't lie
const ubsurd = <A, B>(a: A): B => null as any as B;
const identity = <A>(a: A): A => a;
const constant =
<A, B>(a: A) =>
(b: B): A =>
a;
const always1 = constant(1);
always1("hola"); // 1
always1(false); // 1
constant("foo")(false); // 'foo'
const optionToValue = <A>(optionA: Option.Option<A>): A =>
pipe(
optionA,
Option.match(
() => {
throw new Error("No A");
},
(a) => a
)
);
optionToValue(Option.some(123)); // 123
optionToValue(Option.none); // Error !!!
const lenIs5_option = (s: string): Option.Option<string> =>
s.length === 5 ? Option.some(s) : Option.none;
const lenIs5_either = (s: string): Either.Either<string, string> =>
s.length === 5 ? Either.right(s) : Either.left(`Lenght is ${s.length}`);
interface UserWithLast {
name: string;
last_name?: string;
}
const userHasLastName = (
user: UserWithLast
): Either.Either<string, UserWithLast> =>
user.last_name ? Either.right(user) : Either.left("No last name");
const nameIsLongEnough = (
user: UserWithLast
): Either.Either<string, UserWithLast> =>
pipe(
user.name,
lenIs5_either,
Either.map((_name) => user)
);
// Either<string, UserWithLast>
const sergiy: UserWithLast = { name: "Sergiy" };
pipe(
[userHasLastName(sergiy), nameIsLongEnough(sergiy)],
Either.sequenceArray // Left<'No last name'>
);
const maksym: UserWithLast = { name: "Maksym", last_name: "Borsy..." };
pipe(
[userHasLastName(maksym), nameIsLongEnough(maksym)],
Either.sequenceArray // Right<maksym>
);
const zoe: UserWithLast = { name: "Zoe" };
pipe(
[userHasLastName(zoe), nameIsLongEnough(zoe)],
Either.sequenceArray // Left<'No last name'>
);
// Validation lets acumulate errors
// TODO: find better example of how to work with it xD
type Validation<E, A> = Either.Either<E[], A>;
const validation = Either.getApplicativeValidation(semigroupString);
// traverse : (f: (a: A) => Either<E, B>) => Array<A> => Either<E, Array<B>>
const traverse = ArrayFP.traverse(Either.Applicative);
const allUsersWithLastName = pipe(
[sergiy, zoe, maksym],
traverse(userHasLastName) // Either<string, UserWithLast[]>
);
// Either
const validateNameAndUpercase = (user: UserWithLast) =>
pipe(
user,
userHasLastName, // Either<string, User>
Either.mapLeft((errMsg) => new Error(errMsg)), // Either<Error, User>
Either.map(({ name }) => name.toUpperCase()) // Either<Error, string>
);
const validateNameAndUpercase_ = (user: UserWithLast) =>
pipe(
user,
userHasLastName, // Either<string, User>
Either.map(({ name }) => name.toUpperCase()), // Either<string, string>
Either.mapLeft((errMsg) => new Error(errMsg)) // Either<Error, string>
);
validateNameAndUpercase(zoe); // Left<Error(...)>
validateNameAndUpercase(maksym); // Right<'MAKSYM'>
// What about async code?
const getUser = () => Promise.resolve(zoe);
getUser()
.then(userHasLastName)
.then(Either.map(({ name }) => name.toUpperCase()))
.then(Either.mapLeft((errMsg) => new Error(errMsg)));
pipe(
getUser(),
(p) => p.then(userHasLastName),
(p) => p.then(Either.map(({ name }) => Promise.resolve(name.toUpperCase())))
// Promise<Either<string, Promise<string>>>
);
// traverse : (f: (a: A) => X<E, B>) => Y<A> => X<E, Y<B>>
// Here X and Y are "higher order types" ->
// ie. they are interfaces that implement Traversable and Applicative typeclasses
pipe(
zoe,
userHasLastName, // Either<string, UserWithLast>
Either.map(({ name }) => Promise.resolve(name.toUpperCase())),
// Either<string, Promise<string>>
Either.match(
(err) => Promise.resolve(Either.left(err)),
(promise) => promise.then(Either.right)
),
// Promise<Either<string, string>>
(v: Promise<Either.Either<string, string>>) => v
);
const traversePromiseEither = <E, A>(
e: Either.Either<E, Promise<A>>
): Promise<Either.Either<E, A>> =>
pipe(
e,
Either.match(
(err) => Promise.resolve(Either.left(err)),
(promise) => promise.then(Either.right)
)
);
// fp-ts way: TaskEither (https://gcanti.github.io/fp-ts/modules/TaskEither.ts.html)
const validateNameAndUpercaseAsync = (user: UserWithLast) =>
pipe(
user,
userHasLastName, // Either<string, User>
TaskEither.fromEither,
// TaskEither<Error, User>
TaskEither.mapLeft((errMsg) => new Error(errMsg)),
// TaskEither<Error, string>
TaskEither.map(({ name }) => name.toUpperCase()),
// TaskEither<Error, number>
TaskEither.chain((name) => TaskEither.right(name.length)),
// ^ this could be an actual async function
TaskEither.chain((n) => TaskEither.left(new Error("Failed")))
// ^ this could be an actual async function
);
const getResult = validateNameAndUpercaseAsync(maksym);
getResult()
.then((result) => {
pipe(
result,
Either.match(
(err) => {},
(succ) => {}
)
);
// Either<Error, number>
})
.catch(() => {
console.log("Really unexpected error");
});
// Decoding items in a list independent from each other
// 1. First decode list as => t.arry(t.unkown)
// 2. Then decode each item idependelty and accumulate the errors
//
// https://github.com/catawiki/cw-fulfilment/blob/d4c5f1c99860165da8431b2642d5c3ebb9ec179f/app/assets/client/shared/features/OrderList/api.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment