user6445533
user6445533

Reputation:

What is the purpose of the Reader Monad's local function?

Simply put, the Reader Monad is just a wrapper around a function. It is good for implicitly passing configuration info through a computation:

const ap = f => x => f(x);
const id = x => x;
const co = x => y => x;
const comp = f => g => x => f(g(x));

const Reader = ap(cons => f => {
  const t = new cons();
  t.run = x => f(x);
  return t;
}) (function Reader() {});

Reader.map = f => tf => Reader(comp(f) (tf.run));
Reader.ap = af => ag => Reader(x => af.run(x) (ag.run(x)));
Reader.chain = mf => fm => Reader(x => fm(mf.run(x)).run(x));
Reader.of = f => Reader(co(f));
Reader.ask = () => Reader(id);
Reader.asks = f => Reader.chain(Reader.ask) (x => Reader.of(f(x)));
Reader.local = f => tf => Reader(comp(tf.run) (f));

const compM = tDict => fm => gm => x =>
  tDict.chain(tDict.chain(tDict.of(x)) (gm)) (fm);

const foo = n => Reader(env => (console.log(env), n + 1)),
  c = compM(Reader) (foo) (foo) (2);

console.log(
  c.run("shared environment")
);

However, I couldn't find a good example for local and I lack imagination how to use it. I understand that local is just contramap but that doesn't help much. So what is its purpose?

Upvotes: 1

Views: 692

Answers (1)

Mulan
Mulan

Reputation: 135327

I understand that local is just contramap but that doesn't help much. So what is its purpose?

Well contramap is just as important as map – in the example Bergi linked (translated here into JS), local allows to affect the value before the next reader gets it

const calculateLength =
  Reader.map (s => s.length) (Reader.ask ())

const calculateModifiedLength = 
  Reader.local (s => 'Prefix ' + s) (calculateLength)

console.log (calculateLength.run ('12345'))
// '12345'.length == 5
// => 5    

console.log (calculateModifiedLength.run ('12345'))
// 'Prefix 12345'.length == 12
// => 12

Here's your Reader monad, lovingly reformatted. I included a demo of each function so that we can verify each result is correct. Thanks for making me finally learn about this interesting monad!

const Reader = f =>
  ({ run : x => f (x) })

Reader.of = x =>
  Reader (() => x)

Reader.chain = f => m =>
  Reader (x => f (m.run (x)) .run (x))
  
Reader.map = f => m =>
  Reader (x => f (m.run (x)))

Reader.ap = f => m =>
  Reader (x => f.run (x) (m.run (x)))
  
Reader.join = m =>
  Reader (x => m.run (x) .run (x))

Reader.ask = () =>
  Reader (identity)
  
Reader.local = f => m =>
  Reader (x => m.run (f (x)))

const identity = x => x
const sq = x => x * x
const add = x => y => x + y

const calculateLength =
  Reader.map (s => s.length) (Reader.ask ())

const calculateModifiedLength = 
  Reader.local (s => 'Prefix ' + s) (calculateLength)
  
console.log
  ( Reader.chain (x => Reader.of (x + 1)) (Reader (sq)) .run (4) // 17    sq (4) + 1
  , Reader.chain (x => Reader (add (x))) (Reader (sq)) .run (4)  // 20    sq (4) + 4
  , Reader.map (sq) (Reader (sq)) .run (4)                       // 256   sq (sq (4))
  , Reader.ap (Reader.of (sq)) (Reader.of (4)) .run ()           // 16    sq (4)
  , Reader.ap (Reader (add)) (Reader (sq)) .run (4)              // 20   sq (4) + 4 
  , Reader.join (Reader.of (Reader.of (4))) .run ()              // 4
  , Reader.ask () .run (4)                                       // 4
  , calculateLength .run ('12345')                               // 5    '12345'.length
  , calculateModifiedLength .run ('12345')                       // 12   'Prefix 12345'.length
  )

And here's the type signatures, for those that are interested

Reader :: (e -> a) -> Reader e a
Reader.of :: a -> Reader e a 
Reader.chain :: (a -> Reader e b) -> Reader e a -> Reader e b
Reader.map :: (a -> b) -> Reader e a -> Reader e b  
Reader.ap :: Reader e (a -> b) -> Reader e a -> Reader e b
Reader.join :: Reader e (Reader e a) -> Reader e a   
Reader.ask :: () -> Reader a a
Reader.local :: (e -> e) -> Reader e a -> Reader e a

Upvotes: 1

Related Questions