Map and filter an object using Ramda

ThomasReggi picture ThomasReggi · Jul 29, 2015 · Viewed 8.9k times · Source

I'm learning Ramda and I'm a little confused how to build this lodash chain below using Ramda. Ramda returns functions for it's operations instead of actual values, and this seems to be the focal point of functional programming, however in this example I have a second parameter localRegex that isn't a primary argument. It seems that it wouldn't be possible to get this to be completely duplicated without wrapping the Ramda function and using .apply() or .call() to propagate the wrapped function arguments to the Ramda function, which seems more complicated then using lodash.

var _ = require("lodash")
var R = require("ramda")

var localRegex = /^.\.\/|^.\/|^\//

function getRecursiveDeps(deps, localRegex){
  return _.chain(deps)
    .map(function(dep){
      return dep.source.value
    })
    .filter(function(dep){
      return dep.match(localRegex)
    })
    .value()
}

var items = [
  {
    "source": {
      "value": "./foo"
    }
  },
  {
    "source": {
      "value": "bar"
    }
  }
]

console.log(getRecursiveDeps(items, localRegex))

Here's what I've got and its not working.

var getRecursiveDeps = R.chain(
  R.map(function(dependency){
    return dependency.source.value
  }),
  R.filter(function(value){
    return value.match(localRegex)
  })
)

Is there a way to have Ramda use a main variable for chaining and also pass down localRegex? Is there a way to duplicate the getRecursiveDeps that uses lodash in Ramda?

There's a lot of talk about how Ramda is functional and underscore and lodash aren't. But in this case the getRecursiveDeps is a function that returns a value from lodash. When you create functions like this from lodash or underscore the result is the same, there's just more work when it comes to wrapping it, in this case what would be the perk using Ramda over Lodash?

Answer

davidchambers picture davidchambers · Jul 29, 2015

R.chain does something completely different from _.chain. Its type, according to the current documentation, is (a -> [b]) -> [a] -> [b], though its actual type is more general. Think of it as a "flat-map" function.

What you actually want here is R.compose or its left-to-right equivalent R.pipe.

If the goal of the function is to find local dependencies, it seems appropriate to me to embed the pattern in the function. I would thus write:

// getLocalDeps :: [{ source :: { value :: String }}] -> [String]
const getLocalDeps =
R.pipe(R.map(R.path(['source', 'value'])),
       R.filter(R.test(/^[.]{0,2}[/]/)));

getLocalDeps(items);  // => ['./foo']

I'm a bit confused by the name getRecursiveDeps as the function isn't recursive. getLocalDeps seems more appropriate.


If you'd like to parameterize the pattern, I suggest breaking getLocalDeps into smaller pieces:

// isLocal :: String -> Boolean
const isLocal = R.test(/^[.]{0,2}[/]/);

// getDeps :: [{ source :: { value :: String }}] -> [String]
const getDeps = R.map(R.path(['source', 'value']));

// getLocalDeps :: [{ source :: { value :: String }}] -> [String]
const getLocalDeps = R.pipe(getDeps, R.filter(isLocal));

You could then define other functions in terms of these building blocks:

// getNonLocalDeps :: [{ source :: { value :: String }}] -> [String]
const getNonLocalDeps = R.pipe(getDeps, R.reject(isLocal));

// getLocalJsonDeps :: [{ source :: { value :: String }}] -> [String]
const getLocalJsonDeps = R.pipe(getLocalDeps, R.filter(R.test(/[.]json$/)));