I'm trying to practice function composition using Ramda but wondering if it's overkilled and needed some advice on this.
Given the object below
const articles = [
{
title: 'Everything Sucks',
url: 'http://do.wn/sucks.html',
author: {
name: 'Debbie Downer',
email: 'debbie@do.wn'
}
},
{
title: 'If You Please',
url: 'http://www.geocities.com/milq',
author: {
name: 'Caspar Milquetoast',
email: 'hello@me.com'
}
}
];
Make a boolean function that says whether a given person wrote any of the articles.
Sample function invocation
isAuthor('New Guy', articles) // should return false
isAuthor('Debbie Downer', articles) // should return true
My Solution
First I create a function to grab the author name as below
const get = _.curry(function(x, obj) { return obj[x]; });
const names = _.map(_.compose(get('name'), get('author'))); // ['Debbie Downer', 'Caspar Milquetoast']
Now that I have a function names ready to be used, I will try to construct isAuthor function and below is two of my attempts
Attempt 1: without composition
const isAuthor = function(name, articles) {
return _.contains(name, names(articles))
};
Attempt 2 with composition
const isAuthor = function(name, articles) {
return _.compose(
_.contains(name), names
)(articles);
}
Both attempts works with correct results, this question is asking solely from functional programming perspective as I'm totally new to this domain and wondering which attempt is favourable than the other, and hopefully this question will not get closed as opinion based, I sincerely thinks that this is not opinion based but seeking industrial practice in FP world.
Also feel free to provide any alternatives than the two attempts I've made, thanks!
Ramda has a set of functions to work with nested properties. In your case we could combine pathEq and any:
pathEq returns true if a property at given path is equal to given value.any applies a predicate function to each element of a list until it is satisfied.The isAuthor function takes a name first then returns a function that takes a list:
const {compose, any, pathEq} = R;
const isAuthor = compose(any, pathEq(['author', 'name']));
isAuthor('Debbie Downer')(articles);
//=> true
isAuthor('John Doe')(articles);
//=> false
But this isn't necessarily better than:
const {curry} = R;
const isAuthor = curry((x, xs) => xs.some(y => y?.author?.name === x));
isAuthor('Debbie Downer')(articles);
//=> true
isAuthor('John Doe')(articles);
//=> false
Think of Ramda as a tool that helps you write in a certain way, and not something that dictates how you write your code. Ramda (disclaimer: I'm one of the founders) allows you to write in a more declarative fashion, using nice compositions and pipelines. But that doesn't mean that you need to use it everywhere you could.
The simple vanilla JS answer from tuomokar is probably all you need, but Ramda does have tools that might make that seem more readable to some.
So we could write something like this:
const isAuthor = (name) => (articles) =>
includes (name) (map (path (['author', 'name'])) (articles))
const articles = [{title: 'Everything Sucks',url: 'http://do.wn/sucks.html', author: {name: 'Debbie Downer',email: 'debbie@do.wn'}}, {title: 'If You Please', url: 'http://www.geocities.com/milq', author: {name: 'Caspar Milquetoast', email: 'hello@me.com'}}]
console .log (isAuthor ('New Guy') (articles))
console .log (isAuthor ('Debbie Downer') (articles))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>
<script>const {includes, map, path} = R </script>
Note that your get is built in to Ramda as prop, and that your names can be written as map (path (['author', 'name'])).
We could make this point-free, on our own, or using a tool like http://pointfree.io/. It might look like this:
const isAuthor = compose ((o (__, map (path (['author', 'name'])))), contains)
But I find that far less readable than any of the other suggestions. I wouldn't bother.
Both of your solutions seem like an overkill at least in my opinion.
Without using any libraries, a solution could be as simple as this for example:
const isAuthor = (name, articles) => articles.some((article) => article.author.name === name)
Functional programming is cool, but it doesn't mean you should make things unnecessarily complex.
From your proposals the first one seems a lot more readable than the second one.