Skip to content

Instantly share code, notes, and snippets.

@YBogomolov
Created November 3, 2018 16:41
Show Gist options
  • Save YBogomolov/d38d8b1415329a6f2b0306511361b31e to your computer and use it in GitHub Desktop.
Save YBogomolov/d38d8b1415329a6f2b0306511361b31e to your computer and use it in GitHub Desktop.
Free monads example with fp-ts
import { foldFree, liftF } from 'fp-ts/lib/Free';
import { identity, Identity } from 'fp-ts/lib/Identity';
// Task: get all users named 'John' and make them admins
interface User {
id: number;
name: string;
age: number;
isAdmin: boolean;
}
const fakeUsers: User[] = [
{
id: 1,
name: 'John Connor',
age: 32,
isAdmin: false,
},
{
id: 2,
name: 'John Smith',
age: 18,
isAdmin: false,
},
{
id: 3,
name: 'Mark Brian',
age: 28,
isAdmin: false,
},
];
declare module 'fp-ts/lib/HKT' {
interface URI2HKT<A> {
EitherResult: ResultF<A>;
}
}
const OpsURI = 'EitherResult';
type OpsURI = typeof OpsURI;
/*
https://github.com/gcanti/fp-ts/issues/528#issuecomment-405913924
Roughly speaking:
an operation which normally would return void is modelled with `more: A`
an operation which normally would return a type T is modelled with `more: (t: T) => A`
*/
class Search<A> {
readonly _tag: 'Search' = 'Search';
readonly _A!: A;
readonly _URI!: OpsURI;
constructor(
readonly params: Partial<User>,
readonly more: (users: User[]) => A,
) {}
}
class MakeAdmin<A> {
readonly _tag: 'MakeAdmin' = 'MakeAdmin';
readonly _A!: A;
readonly _URI!: OpsURI;
constructor(
readonly users: User[],
readonly more: (users: User[]) => A,
) {}
}
class Save<A> {
readonly _tag: 'Save' = 'Save';
readonly _A!: A;
readonly _URI!: OpsURI;
constructor(
readonly users: User[],
readonly more: (res: boolean[]) => A,
) {}
}
export type ResultF<A> = Search<A> | MakeAdmin<A> | Save<A>;
const search = (params: Partial<User>) => liftF(new Search(params, (a) => a));
const makeAdmins = (users: User[]) => liftF(new MakeAdmin(users, (a) => a));
const save = (users: User[]) => liftF(new Save(users, (a) => a));
const program = search({ name: 'John' })
.chain((johns) => makeAdmins(johns))
.chain((admins) => save(admins));
const exaustive = (x: never): never => x;
export function identityInterpreter<A>(fa: ResultF<A>): Identity<A> {
switch (fa._tag) {
case 'Search':
return identity.of(fa.more(fakeUsers.filter((u) => u.name.includes(fa.params.name))));
case 'MakeAdmin':
return identity.of(fa.more(fa.users.map((u) => ({ ...u, isAdmin: true}))));
case 'Save':
return identity.of(fa.more(fa.users.map((u) => u.isAdmin)));
}
return exaustive(fa);
}
const result = foldFree(identity)(identityInterpreter, program);
console.log(result.value);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment