Last active
October 23, 2017 06:37
-
-
Save ulve/cc6c59bd491dd372351ca09613e9c4d8 to your computer and use it in GitHub Desktop.
MonadReader
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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