evilsoft / crocks

A collection of well known Algebraic Data Types for your utter enjoyment.
https://crocks.dev
ISC License
1.59k stars 102 forks source link

Add local function to Reader and ReaderT #505

Open svozza opened 3 years ago

svozza commented 3 years ago

Is your feature request related to a problem? Please describe. In Haskell the Reader monad has a function called local that let's you to temporarily modify the environment used by the monad. This is really useful when you want to pass the results of a previous call to functions later on in the computation.

Describe the solution you'd like I would like to Crocks to implement the local function as per Haskell. Interesting this just appears to be contramap but the Haskell implementation is still called local for some reason. I am happy to work on a PR for this if there's an appetite to add this function.

Describe alternatives for how you do this now I toyed with the idea of nesting ReaderT monads but this solution is much cleaner

Code I don't have a REPL but have already got this working locally and it was very easy (I basically just translated from Haskell).

// Reader

  function local(method) {
    return function(fn) {
      if(!isFunction(fn)) {
        throw new TypeError(("Reader." + method + ": Function required"))
      }

      return Reader(e => runWith(fn(e)))
    }
  }

// ReaderT

    function local(fn) {
      if(!isFunction(fn)) {
        throw new TypeError(((type()) + ".map: Function required"))
      }

      return ReaderT(function (e) { return runWith(fn(e)) })
    }

To use it's very simple.

const {Async, ReaderT} = require('crocks');

const ReaderAsync = ReaderT(Async);

// localFunc :: String -> ReaderT e (Async a b)
function localFunc(paramToAdd) {
  return func1() // returns a ReaderAsync 
    .chain(func2) // function requires paramToAdd to be in env
    .chain(func3) // function requires paramToAdd to be in env
    .local(e => ({y: paramToAdd, ...e}) // previous 3 functions will use modified env
    .map(func4) // will use unmodified env
}

getParamToAdd()
.chain(localFunc)
.map(func5)
// ...
.runWith({x: 'param'})
.fork(/* ... */)

Here's some real code that uses this new function if you want something more interesting than the above contrived example:

https://github.com/svozza/functionalish-refactor/blob/master/functional/lib/index.js