Skip to content

Instantly share code, notes, and snippets.

@ulve
Last active October 23, 2017 06:37
Show Gist options
  • Select an option

  • Save ulve/cc6c59bd491dd372351ca09613e9c4d8 to your computer and use it in GitHub Desktop.

Select an option

Save ulve/cc6c59bd491dd372351ca09613e9c4d8 to your computer and use it in GitHub Desktop.
MonadReader
class Reader<E, A> {
private constructor(protected k) {}
Run = (e: E): A => this.k(e);
Bind = <B>(f: (a: A) => Reader<E, B>): Reader<E, B> =>
new Reader(e => f(this.k(e)).Run(e));
Map = <B>(f: (a: A) => B): Reader<E, B> => new Reader(e => f(this.k(e)));
static Ask = <E, A>(): Reader<E, A> => new Reader(x => x);
static Asks = <E, A>(f: (a: E) => A): Reader<E, A> => new Reader(f);
static Unit = <E, A>(f: A): Reader<E, A> => new Reader(() => f);
}
// Simple
var cat = Reader.Ask<string, string>()
.Bind<string>(ctx => Reader.Unit(ctx.toUpperCase()))
.Run("cat");
console.log(cat);
//=> CAT
const Greeter = (name: string): Reader<string, string> =>
Reader.Ask<string, string>().Bind(ctx => Reader.Unit(ctx + ", " + name));
var greet = Greeter("Lisa").Run("Hello");
console.log(greet);
//=> Hello, Lisa
// Composition
const Punctuation = (str: string): Reader<string, string> =>
Reader.Asks<string, boolean>(x => x === "Hello").Bind(ish =>
Reader.Unit(str + (ish ? "!" : "!!!"))
);
const Upper = (str: string): Reader<string, string> =>
Reader.Asks<string, boolean>(x => x === "Hi").Bind(i =>
Reader.Unit(i ? str.toUpperCase() : str)
);
var greet2 = Greeter("Valle")
.Bind(Punctuation)
.Run("Hello");
console.log(greet2);
//=> Hello, Valle!
var greet3 = Greeter("Perka")
.Bind(Punctuation)
.Run("Hi");
console.log(greet3);
//=> Hi, Perka!!!
//
// A bit more advanced
//
// Some interfaces and a dummy database
interface UserRow {
id: string;
name: string;
age: number;
}
interface Db {
users: UserRow[];
}
let dummyDb: Db = {
users: [
{
id: "guid",
name: "John Doe",
age: 28
},
{
id: "gork",
name: "Fofo",
age: 123
}
]
};
// Curried functions that use the db connection
const GetUser = (userId: string) => (db: Db): UserRow =>
db.users.find(u => u.id === userId);
const SetUser = (userId: string, user: UserRow) => (db: Db): UserRow =>
(db.users[userId] = user);
// This function just extracts the user name from a user record
const GetUserName = (userId: string): Reader<Db, string> =>
Reader.Asks<Db, UserRow>(GetUser(userId)).Map(user => user.name);
// This function gets a user by id and updates the name
const SetUserName = (userId: string, newName: string): Reader<Db, UserRow> =>
Reader.Asks<Db, UserRow>(GetUser(userId)).Bind(user => {
user.name = newName;
return Reader.Asks(SetUser(userId, user));
});
// And an overarching function that does some logging
const ChangeUserName = (userId: string, newName: string): Reader<Db, string> =>
GetUserName(userId).Bind(oldName =>
SetUserName(userId, newName).Map(
() => `User '${userId}' : name changed from '${oldName}' to '${newName}'`
)
);
// Lets run this
var result: string = ChangeUserName("guid", "Bertil Doe").Run(dummyDb);
console.log(result);
//=> User 'guid' : name changed from 'John Doe' to 'Bertil Doe'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment