【问题标题】:How can I improve the ramda code I have written?如何改进我编写的 ramda 代码?
【发布时间】:2019-07-14 16:23:22
【问题描述】:

我是 ramda.js 的新手,我编写了以下代码来检查值是否尚未以提供的列表中的后缀结尾,它添加了“px”后缀


function addSuffix(value, suffix = 'px', test = ['px', 'pt', '%']) {
  return compose(
    concat(value),
    ifElse(anyPass(map(endsWith, test)), () => '', () => suffix)
  )(value);
}

【问题讨论】:

    标签: ramda.js


    【解决方案1】:

    也许我提出的第一个建议是避免使用默认参数。它们通常不能很好地处理柯里化函数。

    您可以编写一个函数,该函数返回一个函数来检查字符串是否以给定的后缀列表结尾:

    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'
    

    请注意,您可以使用useWithpointfree 样式重写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,当您尝试像调用函数一样调用数字时会出现错误。

    【讨论】:

    • 感谢您的快速回复。但我希望清单 ['px', 'pt', 'px'] 是动态的。它将作为函数参数出现。如果您查看我的原始代码,我正在使用 map 和 ifElse 来实现它。
    • 确实如此。不过,我们可以应用相同的原则。我会更新
    • 今天早上上车的时候看到了这个问题,脑子里写了一个答案,开始上班正要打字的时候看到你写的几乎一模一样事情,甚至到关于useWith的注释!您编码但未提及的一项重要更改是从 ifElse 切换到不那么笨重的 unless;这通常会使代码更干净。
    • 感谢@ScottSauyet。我刚刚添加了一个关于柯里化函数中默认参数的附录。对此有什么想法吗?
    • @customcommander:我完全同意。因为默认参数对函数的数量没有贡献,所以它们很难与柯里化或任何其他类型的部分应用程序结合使用。不过,我发现了一个地方,它们非常有用:编写递归函数而不需要辅助函数。我不倾向于将递归与部分应用混为一谈,所以这不是问题。
    【解决方案2】:

    你也可以避免传递后缀列表,因为它看起来是多余的......只要确保给定的 value 不以数字结尾:

    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'));
    &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"&gt;&lt;/script&gt;

    【讨论】:

    • 我认为后缀列表的想法是避免在已经存在的地方添加一个css单元,例如px。因此,例如 addPx('15pt') 应该按原样返回值。
    • 在这种情况下,我认为您的算法做出了错误的假设:单位不可替换,15pt15px 非常不同...我认为您正在寻找一个简单的safe add unit,如果不存在,则添加一个单位。更新了我的答案。
    猜你喜欢
    • 2019-08-19
    • 2017-07-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-24
    • 1970-01-01
    • 1970-01-01
    • 2014-11-19
    相关资源
    最近更新 更多