Take the following example imperative JavaScript example:
getAnimalList = (hasCat) => {
const baseAnimals = { dog: animLib.dog(), bear: animLib.bear()};
if(hasCat){
baseAnimals.cat = animLib.cat();
}
return baseAnimals
}
I am trying to write this code in a functional style with Ramda, but the only way I can do so is by having the functions reach outside of scope:
getAnimalList = (hasCat) => {
const baseAnimals = { dog: animLib.dog(), bear: animLib.bear()};
return when(always(hasCat), merge({hasCat: animLib.cat()}))(baseAnimals)
}
Leaving aside how animLib is outside of scope, the way I would fix has hasCat from being pass from the outside(if this is a problem at all) is the following:
getAnimalList = (hasCat) => {
const baseAnimals = { dog: animLib.dog(), bear: animLib.bear()};
const mergeCat = when(compose(equals(true), prop('hasCat')),
compose(merge({hasCat: animLib.cat()}), prop('baseAnimals')));
return mergeCat({hasCat: hasCat, baseAnimals: baseAnimals});
}
But this makes the code incredibly verbose. Is there a better way to do this? Or is the verbosity just the cost of keeping the code more pure.
It seems to me that this would do fine:
getAnimalList = (animLib, hasCat) => {
const baseAnimals = { dog: animLib.dog(), bear: animLib.bear()};
return hasCat ? merge({hasCat: animLib.cat()}, baseAnimals) : baseAnimals;
}
Because Ramda's when and ifElse are based around predicate functions rather than boolean values, using them seems to be overkill here.
Obviously if you want a version that has animLib in scope, you can curry this function, either with something like Ramda's curry or just by changing to
getAnimalList = (animLib) => (hasCat) => { /* ... */ }
Then you can create newAnimalList = getAnimalList(myAnimLib).
Related
I wrote a simple function that adds and removes classes for elements (buttons).
function mainBut (){
ba.classList.add("act2");
ba.classList.remove("hov")
bh1.classList.add("hov");
bh1.classList.remove("act2");
bh2.classList.add("hov");
bh2.classList.remove("act2");
da.classList.remove("none")
dh1.classList.add("none")
dh2.classList.add("none")
}
But as the number of elements grows, i see that the code could be better organized. Because we could:
remove: .act2 for (bh1 and bh2) | add: .hov for (bh1 and bh2) | add: .none for (dh1 and dh2).
I'm wondering, if it could be done using for loop? Or maybe there is a better way...
You can use functional programming to simplify it. Break-in small function and reuse.
const add = cls => elm => elm.classList.add(cls);
const remove = cls => elm => elm.classList.remove(cls);
const addAct2 = add("act2");
const addHov = add("hov");
const removeAct2 = remove("act2");
const removeHov = add("hov");
const addNone = add("none");
const removeNone = add("none");
function mainBut() {
addAct2(ba);
removeHov(ba);
addHov(bh1);
removeAct2(bh1);
addHov(bh2);
removeAct2(bh2);
removeNone(da);
addNone(dh1);
addNone(dh2);
}
// MORE FUNTIONAL
const curry = (fn, arity = fn.length, ...args) =>
arity <= args.length ? fn(...args) : curry.bind(null, fn, arity, ...args);
const apply = curry((fn, data =[]) => data.map(fn))
function mainBut() {
apply(removeHov, apply(addAct2, [ba]))
apply(removeAct2, apply(addHov, [bh1, bh2]))
apply(removeNone, [da])
apply(addNone, [da, dh1, dh2])
}
I have this simple function that I am looking to simplify further:
setAreas() {
this.areas = ipcRenderer.sendSync('request', 'areas').map(_area => {
_area.locations = _area.locations.map(locationId => this.getLocation(locationId))
return _area
})
}
Is there any way to reduce this to a one-liner by performing the map on _area.locations and returning the updated _area?
One option would be to use Object.assign, which will return the base object being assigned to:
setAreas() {
this.areas = ipcRenderer.sendSync('request', 'areas').map(_area => (
Object.assign(_area, { locations: _area.locations.map(locationId => this.getLocation(locationId)) })
));
}
But that's not so readable. I prefer your current code.
Note that .map is appropriate for when you're transfoming one array into another. Here, you're only mutating every object in an existing array; forEach is more appropriate:
setAreas() {
this.areas = ipcRenderer.sendSync('request', 'areas');
this.areas.forEach((a) => a.locations = a.locations.map(locationId => this.getLocation(locationId)))
}
If getLocation only accepts one parameter, you can golf
a.locations = a.locations.map(locationId => this.getLocation(locationId))
down to
a.locations = a.locations.map(this.getLocation.bind(this))
(you could even remove the .bind(this) if this context isn't needed)
You can use destructuring
setAreas() {
this.areas = ipcRenderer.sendSync('request', 'areas').map(_area => ({
..._area, locations: _area.location.map(locationId => this.getLocation(locationId))
})
}
My eslint version is 4.18.2, it would give a warning like this:
Unexpected unnamed function
Expected an assignment or function call and instead saw an expression
When defining functions in such a way:
const farmerIds = a.reduce((function (hash) {
return function (prev, curr) {
!hash[curr.farmerId] && (hash[curr.farmerId] = prev.push(curr));
return prev;
};
}(Object.create(null))), []);
These are two different issues…
Unexpected unnamed function
This is, as pointed out by Constantin, the ESLint rule func-names.
If you don't want to disable this rule, you can either use names for your functions, like so:
const farmerIds = a.reduce((function reducer(hash) {
return function fn(prev, curr) {
!hash[curr.farmerId] && (hash[curr.farmerId] = prev.push(curr));
return prev;
};
}(Object.create(null))), []);
Or, and this I would recommend personally, use arrow functions:
const farmerIds = a.reduce(
(hash => {
return (prev, curr) => {
!hash[curr.farmerId] && (hash[curr.farmerId] = prev.push(curr));
return prev;
};
})(Object.create(null)),
[]
);
Expected an assignment or function call and instead saw an expression
ESLint is complaining about this line, which is indeed an expression, not an assignment or function call:
!hash[curr.farmerId] && (hash[curr.farmerId] = prev.push(curr));
You can rewrite it as an if statement:
if (!hash[curr.farmerId]) {
hash[curr.farmerId] = prev.push(curr);
}
Fixing both
Putting the code examples above together, this code should run without ESLint complaining:
const farmerIds = a.reduce(
(hash => (prev, curr) => {
if (!hash[curr.farmerId]) {
hash[curr.farmerId] = prev.push(curr);
}
return prev;
})(Object.create(null)),
[]
);
Note that I've also removed the curly braces around the body of the first arrow function, which is a nice additional feature of arrows to keep the code more concise.
Another simple way is give the underscore as the function name. For example:
const farmerIds = a.reduce((function _(hash) {...}(...);
If we need to export without declaring, it can be used as below
export default () => {
// your code goes here
}
I am wondering to see if there is a way to destructure objects in javascript with using a variable. Where as I was doing something like this in my function -
mutateTaxon(data) {
const { content } = data;
const { plp } = content || {};
...
This was working fine, however I need to expand this function based off another factor that can change if I need to use data.content (which it is using now) or data.collection. So I have another node on the data - which changes call to call. I am trying something like this -
mutateTaxon(data) {
const match = lowerCase(data.taxonomy.name);
const { match } = data;
const { plp } = match || {};
Where that match variable would evaluate to either content or collection (as expected). This does not seem to work however, maybe it is not possible? I was thinking maybe the match var needed to be evaluated so I tried something like -
const { [[match]] } = data;
which also is not working. Maybe this is not possible, or I am approaching this wrong. I am wondering, is something like this possible? Thanks!
The destructuring syntax would, as Jonas W. said, be a bit more cumbersome than the bracket notation, but nonetheless, this is how you would do it:
mutateTaxon(data) {
const key = lowerCase(data.taxonomy.name);
const { [key]: { plp } = {} } = data;
Demo
const foo = { bar: { plp: 'success' } }
const key = 'bar'
const { [key]: { plp } = {} } = foo
console.log(plp)
And to confirm that default parameter = {} works as expected:
const foo = { }
const key = 'bar'
const { [key]: { plp } = {} } = foo
console.log(plp)
const key = lowerCase(data.taxonomy.name);
const match = data[key];
I dont think that object destructuring is useful here. But if you need that:
const key = lowerCase(data.taxonomy.name);
const {[key]: match} = data;
I'm trying to write a functional library in JavaScript. I'm also using lodash.
What I've got so far, abstractly:
x = _.curry(function(property, data) {
return data.get(property);
});
With this, you can do x(1)(2) and x(1, 2), and everything works as expected.
Say I want to modify the function so that it data can be an array:
x = _.curry(function(property, data) {
if (_.isArray(data)) {
return _.map(data, x(property));
} else {
return item.get(property);
}
});
Now that works great. But I've got 30+ functions. There's got to be a better way than manual if (_.isArray(data)... else... writing for each function. A decorator maybe?
decorate = function(func) {
// return a curried func and handle the aforementioned _.isArray
}
x = decorate(function(property, data) {
if (_.isArray(data)) {
return _.map(data, x(property));
} else {
return item.get(property);
}
});
I'm completely lost on how to write the decorate function: A function that can take arity 2 and arity 3 functions to decorate.
First you wrap the original function with one that maps array arguments, then you curry that wrapped version.
var decorate = function(func) {
var mapIfArray = function(property, data) {
if (_.isArray(data)) {
return _.map(data, _.curry(func)(property));
} else {
return func(property, data);
}
}
return _.curry(mapIfArray);
}
You would use it like this:
var undecorated = function(property, data) {
return data.get(property);
}
var decorated = decorate(undecorated);
That's assuming all your undecorated functions are arity 2, with the possible array at the end. If that's not the case, you'll either have to create different decorators for different arities, or do some sort of voodoo with the arguments object.
I'm not really sure what you're trying to accomplish but could something like this work? This is a bit hacky but idea is to pass the function from the outside.
x = _.curry(function(func, data) {
return _.map(_.flatten([data]), func);
});