Skip to content

Instantly share code, notes, and snippets.

@dypsilon
Last active April 28, 2024 08:50
Show Gist options
  • Select an option

  • Save dypsilon/883e878ca1c05a7c355e41fb28a2f3e3 to your computer and use it in GitHub Desktop.

Select an option

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
Copy Markdown

lin7sh commented Nov 29, 2016

Beautiful

@danielo515
Copy link
Copy Markdown

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
Copy Markdown

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
Copy Markdown

ayoung4 commented Oct 25, 2019

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

@wayneseymour
Copy link
Copy Markdown

Very very nice!

@cawabunga
Copy link
Copy Markdown

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
Copy Markdown

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