翻译版本的`lt`,`lte`,`gt`和`gte`的好名字?

Sco*_*yet 13 javascript functional-programming ramda.js

我已经在一个名为Ramda的Javascript FP库上工作了一段时间,我在命名方面遇到了一些问题.(你听说过这条旧线,对吗?"计算机科学只有两个难题:缓存失效,命名事物和一个一个错误.")

在这个库中,(几乎)多个参数的每个函数都会自动计算.这适用于大多数用例.但是有一些问题是一些函数是非交换二元运算符.问题在于,英语名称往往意味着与应用currying时所发生的不同.例如,

var div10 = divide(10);
Run Code Online (Sandbox Code Playgroud)

听起来它应该是一个将其参数除以10的函数.但实际上它将其参数分为 10,如果你看一下这个定义就很清楚了:

var divide = curry(function(a, b) {
    return a / b;
});
Run Code Online (Sandbox Code Playgroud)

所以相反的预期:

div10(50); //=> 5 // NO!!
Run Code Online (Sandbox Code Playgroud)

事实上,你得到了

div10(50); //=> 0.2 // Correct, but surprising!
Run Code Online (Sandbox Code Playgroud)

我们通过记录从人们的预期可能的差别,并创建处理这个问题divideBy,这只是flip(divide)subtractN,这是flip(subtract).但是我们还没有找到一个很好的等价函数,例如lt:

R.lt = curry(function(a, b) { 
    return a < b;
});
Run Code Online (Sandbox Code Playgroud)

或它的表兄弟lte,gtgte.

我自己的直觉就是这样

map(lt(5), [8, 6, 7, 5, 3, 0, 9]); 
//=> [false, false, false, false, true, true, false]
Run Code Online (Sandbox Code Playgroud)

但当然,它实际上会回归

//=> [true, true, true, false, false, false, true]
Run Code Online (Sandbox Code Playgroud)

所以我想为它的同类做同样的文档和点到备用名称例程lt.但我找不到一个好名字.唯一真正的候选人已经ltVal和使用两个参数调用时并不真正起作用.我们确实讨论了这个问题,但没有得出好的结论.

让其他人处理这个并提出好的解决方案吗?或者即使没有,对这些功能的翻转版本的名称有什么好的建议吗?


更新

有人认为,这可以被关闭,因为"不清楚你问什么,我想真正的问题在解释减轻了一些.简单的问题是:

对于翻转版本,什么是好的,直观的名称lt

Aad*_*hah 26

首先,您要维护的函数式编程库是值得称道的.我一直想写一个,但我从来没有找到时间这样做.

考虑到您正在编写函数式编程库,我将假设您了解Haskell.在Haskell中,我们有函数和运算符.函数始终是前缀.运算符总是中缀.

Haskell中的函数可以使用反引号转换为运算符.例如div 6 3可以写成6 `div` 3.类似地,可以使用括号将运算符转换为函数.例如2 < 3可以写成(<) 2 3.

也可以使用部分部分应用运算符.有两种类型的部分:左侧部分(例如(2 <)(6 `div`))和右侧部分(例如(< 3)(`div` 3)).左侧部分翻译如下:(2 <)变为(<) 2.正确的部分:(< 3)成为flip (<) 3.


在JavaScript中我们只有函数.在JavaScript中创建运算符没有"好"的方法.你可以编写像这样的代码(2).lt(3),但我认为这是粗鲁的,我强烈建议不要编写这样的代码.

因此,我们可以将普通函数和运算符编写为函数:

div(6, 3) // normal function: div 6 3
lt(2, 3) // operator as a function: (<) 2 3
Run Code Online (Sandbox Code Playgroud)

在JavaScript中编写和实现中缀运算符是一件痛苦的事.因此,我们不会有以下内容:

(6).div(3) // function as an operator: 6 `div` 3
(2).lt(3) // normal operator: 2 < 3
Run Code Online (Sandbox Code Playgroud)

部分很重要.让我们从正确的部分开始:

div(3) // right section: (`div` 3)
lt(3) // right section: (< 3)
Run Code Online (Sandbox Code Playgroud)

当我看到div(3)我希望它是一个正确的部分(即它应该表现为(`div` 3)).因此,根据最不惊讶原则,这是应该实施的方式.

现在出现左侧部分的问题.如果div(3)是正确的部分那么左侧部分应该是什么样的?我认为它应该是这样的:

div(6, _) // left section: (6 `div`)
lt(2, _) // left section: (2 <)
Run Code Online (Sandbox Code Playgroud)

对我来说,这就是"以某种方式划分6""比某种东西小2"?我更喜欢这种方式,因为它是明确的.根据Python的禅宗,"明确比隐含更好".


那么这对现有代码有何影响?例如,考虑一下这个filter功能.要过滤列表中的奇数,我们会写filter(odd, list).对于这样的功能,是否按预期进行了工作?例如,我们如何编写filterOdd函数?

var filterOdd = filter(odd);    // expected solution
var filterOdd = filter(odd, _); // left section, astonished?
Run Code Online (Sandbox Code Playgroud)

根据最不惊讶的原则,它应该是简单的filter(odd).该filter功能并不意味着用作操作员.因此,程序员不应该被迫将其用作左侧部分.功能和"功能操作员"之间应该有明确的区别.

幸运的是,区分函数和函数操作符非常直观.例如,该filter函数显然不是函数运算符:

filter odd list -- filter the odd numbers from the list; makes sense
odd `filter` list -- odd filter of list? huh?
Run Code Online (Sandbox Code Playgroud)

另一方面,该elem函数显然是一个函数运算符:

list `elem` n -- element n of the list; makes sense
elem list n -- element list, n? huh?
Run Code Online (Sandbox Code Playgroud)

重要的是要注意,这种区别是唯一可能的,因为函数和函数运算符是互斥的.理所当然,给定一个函数,它可以是普通函数,也可以是函数运算符,但不是两者.


有趣的是,如果你flip的参数给定一个二元函数,那么它就变成二元运算符,反之亦然.例如,考虑的翻转变形filterelem:

list `filter` odd -- now filter makes sense an an operator
elem n list -- now elem makes sense as a function
Run Code Online (Sandbox Code Playgroud)

事实上,对于n大于1的任何n-arity函数,这可以推广.你会看到,每个函数都有一个主要参数.平凡地说,对于一元函数来说,这种区别是无关紧要的.然而,对于非一元函数,这种区别很重要.

  1. 如果函数的主要参数出现在参数列表的末尾,则函数是正常函数(例如filter odd list,where list是主要参数).在列表末尾有主要参数是函数组合所必需的.
  2. 如果函数的主要参数出现在参数列表的开头,则函数是函数运算符(例如list `elem` n,哪个list是主要参数).
  3. 运算符类似于OOP中的方法,主要参数类似于方法的对象.例如,list `elem` nlist.elem(n)在OOP中编写.OOP中的链接方法类似于FP [1]中的函数组合链.
  4. 函数的主要参数可能只在参数列表的开头或结尾处.它在任何其他地方都没有意义.对于二进制函数,此属性是真空的.因此,翻转二进制函数使它们成为运算符,反之亦然.
  5. 其余的参数与函数一起构成了一个不可分割的原子,称为参数列表的词干.例如在filter odd list干是filter odd.在list `elem` n干是(`elem` n).
  6. 词干的顺序和元素必须保持不变,以使表达有意义.这就是为什么odd `filter` list,elem list n没有任何意义.然而list `filter` odd,elem n list因为茎不变,所以有意义.

回到主题,由于函数和函数运算符是互斥的,因此您可以简单地处理函数运算符,而不是处理正常函数的方式.

我们希望运算符具有以下行为:

div(6, 3) // normal operator: 6 `div` 3
div(6, _) // left section: (6 `div`)
div(3)    // right section: (`div` 3)
Run Code Online (Sandbox Code Playgroud)

我们想要定义运算符如下:

var div = op(function (a, b) {
    return a / b;
});
Run Code Online (Sandbox Code Playgroud)

op功能的定义很简单:

function op(f) {
    var length = f.length, _; // we want underscore to be undefined
    if (length < 2) throw new Error("Expected binary function.");
    var left = R.curry(f), right = R.curry(R.flip(f));

    return function (a, b) {
        switch (arguments.length) {
        case 0: throw new Error("No arguments.");
        case 1: return right(a);
        case 2: if (b === _) return left(a);
        default: return left.apply(null, arguments);
        }
    };
}
Run Code Online (Sandbox Code Playgroud)

op函数类似于使用反引号将函数转换为Haskell中的运算符.因此,您可以将其添加为Ramda的标准库函数.在文档中还要提到运算符的主要参数应该是第一个参数(即它应该看起来像OOP,而不是FP).


[1]在旁注中,如果Ramda允许您编写函数,就好像它是在常规JavaScript中链接方法(例如foo(a, b).bar(c)代替compose(bar(c), foo(a, b))),那将是非常棒的.这很难,但可行.

  • 这是一个**极好的**答案!它不仅为我的问题提供了一个潜在的解决方案(在实施之前我必须与团队讨论这个问题),而且还提供了对一般问题的一些极好的见解。非常感谢。 (3认同)
  • JS 中的中缀函数和左/右部分: `$ = (x, f, y) =&gt; f(x) (y); $_ = (x, f) =&gt; f(x); _$ = (f, y) =&gt; x =&gt; f(x) (y); 子 = x =&gt; y =&gt; x - y; $(2, 子, 3); $_(2, 子) (3); _$(子, 3) (2)`。正如您所看到的,在这种情况下,我更喜欢视觉名称(`$`、`$_`、`_$`)而不是文本名称。不幸的是,仍然不如 Haskell 那样简洁:( (2认同)