也许我提出的第一个建议是避免使用默认参数。它们通常不能很好地处理柯里化函数。
您可以编写一个函数,该函数返回一个函数来检查字符串是否以给定的后缀列表结尾:
const hasSuffixFn = compose(anyPass, map(endsWith));
const hasSuffix = hasSuffixFn(['px', 'pt', '%']);
hasSuffix('foo'); // false
hasSuffix('1px'); // true
然后你可以有一个函数,它接受一个后缀列表和一个后缀,并返回一个函数,如果它不存在,它将附加该后缀:
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'
请注意,您可以使用useWith 以pointfree 样式重写addSuffix:
const addSuffix = useWith(unless, [hasSuffix, flip(concat)]);
总而言之
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>
附录
为什么不使用默认参数?
我不知道在函数式编程中使用默认参数是否是一种推荐做法,但我意识到,当您需要部分应用函数时,它们通常会妨碍您。
看看这个:
这里我们有一个添加三个数字的函数:
const foo = (a, b, c) => a + b + c;
foo(10, 20, 30); // 60
我们可以对此进行柯里化并开始部分应用该函数:
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
让我们编写一个类似的函数,但使用默认参数。这按预期工作:
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
但是,如果你想对它进行 curry 并部分应用它,那么你会得到错误:
const bar_curried = curry(bar);
bar_curried(); // 60
bar_curried(110); // 160
bar_curried(110)(220, 330); // Error!
为什么?一个柯里化函数会一直等到你提供了它的所有参数。在那之前,它一直返回一个接受剩余参数的函数。但是,如果您部分应用具有默认参数的函数,那么您无法真正预测 curried 函数将返回什么:最终结果还是接受剩余参数的函数?
在最后一个示例中,bar_curried(110) 直接返回结果,即110 + 20 + 30,当您尝试像调用函数一样调用数字时会出现错误。