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
?
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$/)));
Another way to do this, points-free and preserving your existing API is like this:
// getLocalDeps :: [{ source :: { value :: String }}] -> RegExp -> [String]
const getLocalDeps = R.useWith(
R.flip(R.call),
R.map(R.path(['source', 'value'])),
R.pipe(R.unary(R.test), R.filter)
);
localDeps(items, localRegex); //=> ["./foo"]
The last line of the function feels a bit unfortunate to me, and this question has caused me to open an issue about reverting some recent changes to the library. There are several variants that could be used:
// ...
R.pipe(regex => item => R.test(regex, item), R.filter)
//...
or
// ...
regex => R.filter(R.test(regex))
//...
But before a recent change to Ramda, it would have been simply
// ...
R.pipe(R.test, R.filter)
//...
One thing, though, is that Ramda tries hard to keep parameters in a logical order: with those less likely to change coming before those more likely to change. With that in mind, I would prefer something like this:
// getLocalDeps :: RegExp -> [{ source :: { value :: String }}] -> [String]
var getLocalDeps2 = R.useWith(
R.call,
R.pipe(R.unary(R.test), R.filter),
R.map(R.path(['source', 'value']))
);
localDeps2(localRegex, items); //=> ["./foo"]
And that feels cleaner still. Moreover, it lets you predefine the function and use it separately:
myDeps = localDeps2(localRegex);
myDeps(items); //=> ["./foo"]
And that is a good part of what Ramda is about.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With