Reader Monad in JavaScript

October 6, 2016

Imagine we have some functions that take the same argument:

function getUser(userId) {
  return function(db) {
    return db.users[userId];
  };
}

function setUser(userId, user) {
  return function(db) {
    db.users[userId] = user;
  };
}

Write a function reader that lets us compose them into db-dependent functions:

function getUserName(userId) {
  return reader(getUser(userId)).map(function(user) {
    return user.name;
  });
}

function setUserName(userId, newName) {
  return reader(getUser(userId)).bind(function(user) {
    user.name = newName;
    return reader(setUser(userId, user));
  });
}

function changeUserName(userId, newName) {
  return getUserName(userId).bind(function(oldName) {
    return setUserName(userId, newName).map(function() {
      return [ 'User'
             , userId
             , ': name changed from'
             , oldName
             , 'to'
             , newName
             ].join(' ');
    });
  });
}

This lets us invoke changeUserName by injecting a db:

var db = {
  users: {
    42: {
      name: 'John Doe',
    },
  },
};

var result = changeUserName(42, 'Jack Doe').run(db);

console.log(result); // User 42 : name changed from John Doe to Jack Doe

Solution

Here is one possible solution.
function reader(k) {
  return {
    run: function(e) {
      return k(e);
    },
    bind: function(f) {
      return reader(function(e) {
        return f(k(e)).run(e);
      });
    },
    map: function(f) {
      return reader(function(e) {
        return f(k(e));
      });
    },
  };
}

Demo

This file is literate JavaScript, and can be run using Codedown:

$ curl https://earldouglas.com/exercises/javascript-reader.md |
  codedown javascript |
  node
User 42 : name changed from John Doe to Jack Doe