Skip to content

Instantly share code, notes, and snippets.

@dypsilon
Last active September 12, 2022 23:37
Show Gist options
  • Star 26 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save dypsilon/883e878ca1c05a7c355e41fb28a2f3e3 to your computer and use it in GitHub Desktop.
Save dypsilon/883e878ca1c05a7c355e41fb28a2f3e3 to your computer and use it in GitHub Desktop.
Example usage of the reader monad.
/**
* This short program will encrypt the user password
* and insert a new record into a mock database.
*/
const Reader = require('fantasy-readers');
const R = require('ramda');
const crypto = require('crypto');
// our mock database
const database = [
{ email: 'user@example.org', password: 'e0538fd8f022bb3b139d72cf12766cb0e31690ff' },
{ email: 'admin@example.org', password: '42c4fbf6fec201c66b82c97833b08d936d2cd526' }
]
// creates a statefull database connection
const connectTo = (db) => {
return {
insert: (doc) => db.push(doc),
get: (i) => db[i],
delete: (i) => db.splice(i, 1),
list: () => db
}
}
// some utility functions
const encrypt = (i) => crypto.createHash('sha1').update(i).digest('hex');
const encPassword = R.evolve({password: encrypt})
const getInput = () => ({ email: 'new@example.org', password: 'secret' });
// this is how you access the db connection inside the reader
const save = (user) => {
return Reader.ask.map((db) => {
db.insert(user);
return db.list();
});
}
// the body of the program
const prog = R.pipe(
Reader.of,
R.map(encPassword),
R.chain(save)
);
// this is our db connection now
const dbCon = connectTo(database);
// this is how you pass the db connection in
const result = prog(getInput()).run(dbCon);
// show the output
console.log(result);
@lin7sh
Copy link

lin7sh commented Nov 29, 2016

Beautiful

@danielo515
Copy link

This is nice, but quite simplistic. I would love to see an implementation involving other monads for async operations (which DB interactions usually are)

@danny-andrews
Copy link

danny-andrews commented Jul 21, 2018

@danielo515 I ran into the same issue when I was trying to use a Reader Monad for dependency injection. I was having to do a bunch of nested 'map's to get to the value inside the promise. But then I learned about monad transformers! I wrote one called ReaderPromise which seems to do the trick. https://github.com/danny-andrews/circleci-weigh-in/blob/master/src/shared/reader-promise.js

@ayoung4
Copy link

ayoung4 commented Oct 25, 2019

used this technique in a big refactor and it is great!

@wayneseymour
Copy link

Very very nice!

@cawabunga
Copy link

cawabunga commented Oct 30, 2020

I'm trying to understand this monad, have low understanding of FP. Why can't we just curry the save function? What am I missing?

// this is how you access the db connection inside the reader
const save = user => db => {
    db.insert(user);
    return db.list();
};

// the body of the program
const prog = R.pipe(encPassword, save);

// this is our db connection now
const dbCon = connectTo(database);

// this is how you pass the db connection in
const result = prog(getInput())(dbCon);

UPD:
Got it. Seems in this particular case there is no difference. But in case we want manipulate with result of saving, then Reader monad works like a charm.

const prog = R.pipe(
    Reader.of,
    R.map(encPassword),
    R.chain(save),
    R.map(console.log), // <-- here is the point
);

@lambdaydoty
Copy link

lambdaydoty commented Dec 3, 2020

This is nice, but quite simplistic. I would love to see an implementation involving other monads for async operations (which DB interactions usually are)

You can check the state monad implementation by
https://github.com/dicearr/monastic
Exploit it as as a reader monad like

const { get: ask } = require ('monastic')
...

and for async computation, combine StateT with F.Future from
https://github.com/fluture-js/Fluture

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment