Reputation: 551
I am new to ramda.js, I have written the following code which checks if the value doesn't already end with the suffix in the provided list, it adds "px" suffix
function addSuffix(value, suffix = 'px', test = ['px', 'pt', '%']) {
return compose(
concat(value),
ifElse(anyPass(map(endsWith, test)), () => '', () => suffix)
)(value);
}
Upvotes: 0
Views: 95
Reputation: 14199
you could also avoid passing the list of suffixes, since it looks to be redundant... just make sure that the given value
doesn't end with a digit:
const ensureSuffix = R.curry((suffix, value) =>
R.unless(R.test(/\D$/), v => `${v}${suffix}`, value),
);
const addPx = ensureSuffix('px');
console.log(addPx(15));
console.log(addPx('15pt'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
Upvotes: 0
Reputation: 18961
Perhaps the first suggestion I'd make is to avoid working with default parameters. They usually don't play well with curried functions.
You can write a function that returns a function to checks whether a string ends up with a given list of suffixes:
const hasSuffixFn = compose(anyPass, map(endsWith));
const hasSuffix = hasSuffixFn(['px', 'pt', '%']);
hasSuffix('foo'); // false
hasSuffix('1px'); // true
Then you can have a function that takes a list of suffixes and a suffix and returns a function that will append that suffix if it isn't present already:
const addSuffix = (suffixes, suffix) => unless(hasSuffix(suffixes), flip(concat)(suffix));
const addPx = addSuffix(['px', 'pt', '%'], 'px');
addPx('10'); // '10px'
addPx('10px'); // '10px'
addPx('10pt'); // '10pt'
Please note that you could rewrite addSuffix
in a pointfree style with useWith
:
const addSuffix = useWith(unless, [hasSuffix, flip(concat)]);
Putting it altogether
const hasSuffix = compose(anyPass, map(endsWith));
const addSuffix = (suffixes, suffix) => unless(hasSuffix(suffixes), flip(concat)(suffix));
const addPx = addSuffix(['px', 'pt', '%'], 'px');
console.log(addPx('10'));
console.log(addPx('10px'));
console.log(addPx('10pt'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {anyPass, endsWith, unless, flip, concat, compose, map} = R;</script>
APPENDIX
Why not using default parameters?
I don't know if using default parameters is a recommend practice in functional programming but I have realised that they usually get in the way when you need to partially apply a function.
Take a look at this:
Here we have a function that adds three numbers:
const foo = (a, b, c) => a + b + c;
foo(10, 20, 30); // 60
We can curry this and start partially applying that function:
const foo_curried = curry(foo);
foo_curried(10, 20, 30); // 60
foo_curried(10, 20)(30); // 60
foo_curried(10)(20, 30); // 60
foo_curried(10)(20)(30); // 60
Let's write a similar function but with default parameters. This works as expected:
const bar = (a=10, b=20, c=30) => a + b + c;
bar(); // 60
bar(110); // 160
bar(110, 220); // 360
bar(110, 220, 330); // 660
However if you want to curry this and partially apply it, then you'll get errors:
const bar_curried = curry(bar);
bar_curried(); // 60
bar_curried(110); // 160
bar_curried(110)(220, 330); // Error!
Why? A curried function waits until you've provided all its arguments. Until then it keeps returning a function that accept the remaining arguments. However if you partially apply a function that has default parameters then you can't really predict what the curried function will return: the final result or a function that accepts the remaining argument?
In the last example, bar_curried(110)
returns the result directly, i.e. 110 + 20 + 30
and the error arises when you try to invoke a number as if it was a function.
Upvotes: 2