javascript - Good names for flipped versions of `lt`, `lte`, `gt`, and `gte`? - Stack Overflow

I've been working for some time on a Javascript FP library called Ramda, and I'm having a sli

I've been working for some time on a Javascript FP library called Ramda, and I'm having a slight problem with naming things. (You've heard the old line, right? "There are only two hard problems in Computer Science: cache invalidation, naming things, and off-by-one errors.")

In this library, (almost) every function of more than one parameter is automatically curried. And this works well for most use-cases. But there are some issues with a few functions which are non-commutative binary operators. The issue is that the English names often tend to imply something different than what happens when currying is applied. For example,

var div10 = divide(10);

sounds like it should be a function that divides its parameter by 10. But in fact it divides its parameter into 10, which is pretty clear if you look at the definition:

var divide = curry(function(a, b) {
    return a / b;
});

So instead the expected:

div10(50); //=> 5 // NO!!

In fact, you get

div10(50); //=> 0.2 // Correct, but surprising!

We handle this by documenting the difference from people's possible expectations, and creating divideBy, which is just flip(divide) and subtractN, which is flip(subtract). But we haven't found a good equivalent for functions such as lt:

R.lt = curry(function(a, b) { 
    return a < b;
});

or its cousins lte, gt, and gte.

My own intuition would be that

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

But of course, it actually returns

//=> [true, true, true, false, false, false, true]

So I'd like to do the same document-and-point-to-alternate-name routine for lt and its ilk. But I haven't been able to find a good name. The only real candidate has been ltVal and that doesn't really work when called with both arguments. We did discuss this issue, but had no good conclusions.

Have others dealt with this and come up with good solutions? Or even if not, any good suggestions for a name for the flipped versions of these functions?


Update

Someone suggested that this be closed because 'unclear what you were asking', and I guess the question really was lost a bit in the explanation. The simple question is:

What would be a good, intuitive name for a flipped version of lt?

I've been working for some time on a Javascript FP library called Ramda, and I'm having a slight problem with naming things. (You've heard the old line, right? "There are only two hard problems in Computer Science: cache invalidation, naming things, and off-by-one errors.")

In this library, (almost) every function of more than one parameter is automatically curried. And this works well for most use-cases. But there are some issues with a few functions which are non-commutative binary operators. The issue is that the English names often tend to imply something different than what happens when currying is applied. For example,

var div10 = divide(10);

sounds like it should be a function that divides its parameter by 10. But in fact it divides its parameter into 10, which is pretty clear if you look at the definition:

var divide = curry(function(a, b) {
    return a / b;
});

So instead the expected:

div10(50); //=> 5 // NO!!

In fact, you get

div10(50); //=> 0.2 // Correct, but surprising!

We handle this by documenting the difference from people's possible expectations, and creating divideBy, which is just flip(divide) and subtractN, which is flip(subtract). But we haven't found a good equivalent for functions such as lt:

R.lt = curry(function(a, b) { 
    return a < b;
});

or its cousins lte, gt, and gte.

My own intuition would be that

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

But of course, it actually returns

//=> [true, true, true, false, false, false, true]

So I'd like to do the same document-and-point-to-alternate-name routine for lt and its ilk. But I haven't been able to find a good name. The only real candidate has been ltVal and that doesn't really work when called with both arguments. We did discuss this issue, but had no good conclusions.

Have others dealt with this and come up with good solutions? Or even if not, any good suggestions for a name for the flipped versions of these functions?


Update

Someone suggested that this be closed because 'unclear what you were asking', and I guess the question really was lost a bit in the explanation. The simple question is:

What would be a good, intuitive name for a flipped version of lt?

Share Improve this question edited Aug 29, 2015 at 18:52 Scott Sauyet asked Sep 4, 2014 at 20:37 Scott SauyetScott Sauyet 50.8k5 gold badges56 silver badges110 bronze badges 14
  • In composition I put the "receiver" at the end, so div = curry (x, y) => y / x. This is the behavior I would expect by default. – elclanrs Commented Sep 4, 2014 at 20:39
  • 4 Surely the opposite of lt is gte? – Oliver Charlesworth Commented Sep 4, 2014 at 20:41
  • @OliCharlesworth: pretty sure he's asking about the difference between lt and flip(lt) given that lt is curried, lt(10)(5) isn't the same as flip(lt)(10)(5) – elclanrs Commented Sep 4, 2014 at 20:43
  • @elclanrs: According to the OP's final example, it's simply doing a gte rather than the "expected" lt. – Oliver Charlesworth Commented Sep 4, 2014 at 20:45
  • @OliCharlesworth: Oh I see what you mean, because gt = flip(lt) – elclanrs Commented Sep 4, 2014 at 20:50
 |  Show 9 more comments

2 Answers 2

Reset to default 29

First of all, kudos on the functional programming library that you are maintaining. I've always wanted to write one myself, but I've never found the time to do so.

Considering the fact that you are writing a functional programming library I'm going to assume that you know about Haskell. In Haskell we have functions and operators. Functions are always prefix. Operators are always infix.

Functions in Haskell can be converted into operators using backticks. For example div 6 3 can be written as 6 `div` 3. Similarly operators can be converted into functions using parentheses. For example 2 < 3 can be written as (<) 2 3.

Operators can also be partially applied using sections. There are two types of sections: left sections (e.g. (2 <) and (6 `div`)) and right sections (e.g. (< 3) and (`div` 3)). Left sections are translated as follows: (2 <) becomes (<) 2. Right sections: (< 3) becomes flip (<) 3.


In JavaScript we only have functions. There is no “good” way to create operators in JavaScript. You can write code like (2).lt(3), but in my humble opinion it is uncouth and I would strongly advise against writing code like that.

So trivially we can write normal functions and operators as functions:

div(6, 3) // normal function: div 6 3
lt(2, 3) // operator as a function: (<) 2 3

Writing and implementing infix operators in JavaScript is a pain. Hence we won't have the following:

(6).div(3) // function as an operator: 6 `div` 3
(2).lt(3) // normal operator: 2 < 3

However sections are important. Let's start with the right section:

div(3) // right section: (`div` 3)
lt(3) // right section: (< 3)

When I see div(3) I would expect it to be a right section (i.e. it should behave as (`div` 3)). Hence, according to the principle of least astonishment, this is the way it should be implemented.

Now comes the question of left sections. If div(3) is a right section then what should a left section look like? In my humble opinion it should look like this:

div(6, _) // left section: (6 `div`)
lt(2, _) // left section: (2 <)

To me this reads as “divide 6 by something” and “is 2 lesser than something?” I prefer this way because it is explicit. According to The Zen of Python, “Explicit is better than implicit.”


So how does this affect existing code? For example, consider the filter function. To filter the odd numbers in a list we would write filter(odd, list). For such a function does currying work as expected? For example, how would we write a filterOdd function?

var filterOdd = filter(odd);    // expected solution
var filterOdd = filter(odd, _); // left section, astonished?

According to the principle of least astonishment it should simply be filter(odd). The filter function is not meant to be used as an operator. Hence the programmer should not be forced to use it as a left section. There should be a clear distinction between functions and “function operators”.

Fortunately distinguishing between functions and function operators is pretty intuitive. For example, the filter function is clearly not a function operator:

filter odd list -- filter the odd numbers from the list; makes sense
odd `filter` list -- odd filter of list? huh?

On the other hand the elem function is clearly a function operator:

list `elem` n -- element n of the list; makes sense
elem list n -- element list, n? huh?

It's important to note that this distinction is only possible because functions and function operators are mutually exclusive. It stands to reason that given a function it may either be a normal function or else a function operator, but not both.


It's interesting to note that given a binary function if you flip its arguments then it becomes a binary operator and vice versa. For example consider the flipped variants of filter and elem:

list `filter` odd -- now filter makes sense an an operator
elem n list -- now elem makes sense as a function

In fact this could be generalized for any n-arity function were n is greater than 1. You see, every function has a primary argument. Trivially, for unary functions this distinction is irrelevant. However for non-unary functions this distinction is important.

  1. If the primary argument of the function comes at the end of the argument list then the function is a normal function (e.g. filter odd list where list is the primary argument). Having the primary argument at the end of the list is necessary for function composition.
  2. If the primary argument of the function comes at the beginning of the argument list then the function is a function operator (e.g. list `elem` n where list is the primary argument).
  3. Operators are analogous to methods in OOP and the primary argument is analogous to the object of the method. For example list `elem` n would be written as list.elem(n) in OOP. Chaining methods in OOP is analogous to function composition chains in FP[1].
  4. The primary argument of the function may only be either at the beginning or at the end of the argument list. It wouldn't make sense for it to be anywhere else. This property is vacuously true for binary functions. Hence flipping binary functions makes them operators and vice-versa.
  5. The rest of the arguments along with the function form an indivisible atom called the stem of the argument list. For example in filter odd list the stem is filter odd. In list `elem` n the stem is (`elem` n).
  6. The order and the elements of the stem must remain unchanged for the expression to make sense. This is why odd `filter` list and elem list n don't make any sense. However list `filter` odd and elem n list make sense because the stem is unchanged.

Coming back to the main topic, since functions and function operators are mutually exclusive you could simply treat function operators differently than the way you treat normal functions.

We want operators to have the following behavior:

div(6, 3) // normal operator: 6 `div` 3
div(6, _) // left section: (6 `div`)
div(3)    // right section: (`div` 3)

We want to define operators as follows:

var div = op(function (a, b) {
    return a / b;
});

The definition of the op function is simple:

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);
        }
    };
}

The op function is similar to using backticks to convert a function into a operator in Haskell. Hence you could add it as a standard library function for Ramda. Also mention in the docs that the primary argument of an operator should be the first argument (i.e. it should look like OOP, not FP).


[1] On a side note it would be awesome if Ramda allowed you to compose functions as though it was chaining methods in regular JavaScript (e.g. foo(a, b).bar(c) instead of compose(bar(c), foo(a, b))). It's difficult, but doable.

We all know that naming in programming is serious business, particularly when it comes to functions in curried form. It is a valid solution to handle this issue with a programmatic approach as Aadit did in his response. However, I see two issues with his implementation:

  • it introduces function operators with left/right sections in Javascript, which are not part of the language
  • it requires a hideous placeholder or undefined hack to achieve that

Javascript has no curried operators and thus no left or right sections. An idiomatic Javascript solution should consider that.

The cause of the problem

Curried functions don't have a notion of arity, because every function invocation requires exactly one argument. You can either partially or completely apply curried functions without any helpers:

const add = y => x => x + y;

const add2 = add(2); // partial application

add(2)(3); // complete application

Usually the last argument of a function is its primarily one, because it is passed through function compositions (similar to objects that allow method chaining). Consequently when you partially apply a function, you want to pass its initial arguments:

const comp = f => g => x => f(g(x));

const map = f => xs => xs.map(x => f(x));

const inc = x => x + 1;

const sqr = x => x * x;


comp(map(inc)) (map(sqr)) ([1,2,3]); // [2,5,10]

Operator functions are special in this regard. They are binary functions that reduce their two arguments to a single return value. Since not every operator is commutative (a - b !== b - a) the argument order matters. For this reason operator functions don't have a primarily argument. But people are accustomed to read expressions with them in a certain way depending on the type of application:

const concat = y => xs => xs.concat(y);

const sub = y => x => x - y;


// partial application:

const concat4 = concat(4);

const sub4 = sub(4);

concat4([1,2,3]); // [1,2,3,4] - OK

sub4(3); // -1 - OK


// complete application:

concat([1,2,3]) (4); // [4,1,2,3] - ouch!

sub(4) (3); // -1 - ouch!

We defined concat and sub with flipped arguments, so that partial application works as expected. This evidently doesn't apply to complete application though.

A manual solution

const flip = f => y => x => f(x) (y);

const concat_ = flip(concat);

const sub_ = flip(sub);


concat_(xs) (4); // [1,2,3,4] - OK

sub_(4) (3); // 1 - OK

concat_ and sub_ correspond to left sections in Haskell. Please note that function operators like add or lt don't need a left section version because the former are commutative and the latter are predicates, which have logical counterparts:

const comp2 = f => g => x => y => f(g(x) (y));

const map = f => xs => xs.map(x => f(x));

const flip = f => y => x => f(x) (y);

const not = x => !x;

const notf2 = comp2(not);


const lt = y => x => x < y;

const gt = flip(lt);

const lte = notf2(gt);

const gte = notf2(lt);


map(lt(5)) ([8, 6, 7, 5, 3, 0, 9]);
// [false, false, false, false, true, true, false]

map(gte(5)) ([8, 6, 7, 5, 3, 0, 9]);
// [true, true, true, true, false, false, true]

Conclusion

We should solve this naming issue rather with a naming convention then with a programmatic solution which extends Javascript in a non-idiomatic way. Naming conventions are not ideal...well, just like Javascript.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1738320435a4036410.html

相关推荐

  • 「开源版GPT

    机器之心原创作者:张倩前段时间,GPT-4o 火出了圈,其断崖式提升的生图、改图能力让每个人都想尝试一下。虽然 OpenAI 后来宣布免费用户也可以用,但出图慢、次数受限仍然困扰着没有订阅 ChatGPT 的普通人。那除了 GPT-4o,我

    2小时前
    00
  • 国内开源医疗模型研究报告

    引言随着人工智能技术的快速发展,医疗AI领域正经历前所未有的变革。开源医疗模型作为这一领域的核心技术基础设施,不仅推动了医疗智能化进程,也为医疗工作者提供了强大的辅助工具。本报告将深入探讨国内优秀的开源医疗模型,分析它们的技术特点、应用场景

    1小时前
    00
  • qt中解决#include “main.moc“问题

    根据您提供的信息,您正在使用 Linux 系统,并且系统提示 qmake 命令未找到。以下是安装 qmake 和配置环境变量的步骤:安装 qmake 和 Qt 开发工具安装 Qt 开发工具在 Ubuntu 或 Debian 系统上,您可以使

    1小时前
    00
  • 【HarmonyOS Next之旅】DevEco Studio使用指南(十四)

    1 -> 生成胶水代码函数框架DevEco Studio提供跨语言代码编辑功能。当开发者需要使用NAPI封装暴露给ArkTSJS的接口时,在Cpp头文件内,通过右键Generate > NAPI,快速生成当前函数或类的胶水代码

    1小时前
    00
  • 【今日三题】经此一役小红所向无敌(模拟)连续子数组最大和(动态规划)非对称之美(贪心)

    经此一役小红所向无敌 简单枚举会超时,可以根据题意做一下最大优化,先求出对立和光的血量最多能接收多少次对方的攻击,在判断剩下的最后一次攻击。代码语言:javascript代码运行次数:0运行复制#include <iostream&g

    1小时前
    00
  • 【Linux篇】ELF文件及其加载与动态链接机制

    ELF文件及其加载与动态链接机制一. EFL文件1.1 ELF文件结构二. ELF文件形成与加载2.1 ELF形成可执行2.2 ELF控制性文件的加载2.2.1总结三. ELF加载与进程地址空间3.1 动态链接与动态库加载3.1.1 进程如

    1小时前
    00
  • ENSP 一直####的报错的非常规解决方法

    原文2022年11月17日更新于我的博客园&#xff0c;现在是搬运来CSDN了 报错提示&#xff1a;一直#### 超过了正常的开启时间。 报错环境&#xff1a;工作网络&#xff0c;电脑安装了亚

    1小时前
    00
  • 【用ChatGPT学编程】——如何让AI帮你写代码注释和Debug?

    【前言】 在软件开发的道路上,编写清晰的代码注释和高效Debug是每位开发者的必修课。随着人工智能技术的发展,ChatGPT这类强大的语言模型为我们提供了新的学习和工作方式。本文将详细介绍如何借助ChatGPT完成代码注释编写和Debug,

    1小时前
    00
  • 404 与 Whitelabel Error Page

    关于多种404页面的探讨第一个404场景"Whitelabel Error Page" 通常是 Spring Boot 应用程序的默认错误页面。这表明请求的 URL 在应用程序中没有找到对应的映射,因此返回了 404 错

    1小时前
    00
  • MinecraftForge

    什么是 MinecraftForge?MinecraftForge 是一个为 Minecraft 提供的开源模组开发框架,它是 Minecraft 社区中最受欢迎的模组开发工具之一。通过 Forge,开发者可以轻松创建和加载模组(Mods

    1小时前
    00
  • mac下解压jar包

    在 Mac 环境下使用 unzip xxx.jar -d outputDir 命令的详细教程在日常开发中,.jar 文件经常被用作 Java 应用程序的可执行包或者是库文件。.jar 文件本质上是一个遵循 ZIP 格式的压缩文件,因此我

    1小时前
    00
  • 独立开发者工具 • 半月刊 • 第 008 期

    alt textIndie Tools - 专注于分享独立开发:出海、精选、最新、最实用的工具和资源独立开发者必备网站:半月刊订阅:快捷订阅如果本文能给你提供启发和帮助,感谢各位小伙伴动动小手指,一键三连 (点赞、评论、转发),给我一些支持

    55分钟前
    00
  • 高效阅读AI论文的秘诀——如何快速吸收最前沿的知识

    高效阅读AI论文的秘诀——如何快速吸收最前沿的知识引言近年来,人工智能(AI)的发展日新月异,每天都有大量新论文发布。对于想要深入理解AI技术的开发者、研究者甚至爱好者来说,阅读论文是获取最新知识的必要途径。但面对深奥的数学公式、复杂的实验

    44分钟前
    00
  • 5G到底有多牛?一文看懂它的原理与优势!

    5G到底有多牛?一文看懂它的原理与优势!在互联网时代,网络速度决定了一切。4G改变了我们的生活,让流媒体、移动支付、短视频成为现实。而如今,5G技术正以前所未有的速度,推动全球科技进入新的纪元。那么,5G到底有什么黑科技?为什么它能比4G快

    40分钟前
    00
  • 别光堆数据,架构才是大数据的灵魂!

    别光堆数据,架构才是大数据的灵魂!在这个数据爆炸的时代,随便打开一个应用,都是TB级的数据流。企业都想用大数据挖掘价值,但很多人误以为“大数据=数据量大”。其实,大数据的核心不只是存,而是如何让数据高效流动、高效计算、高效服务业务。这就必须

    37分钟前
    00
  • 红队专题-漏洞挖掘-代码审计-CSRFSSRFXss

    漏洞挖掘-代码审计-CSRFSSRFXss CSRFXSRF Cross—Site Request Forgery 请求伪造 跨站点请求伪造CSRF防御CSRF与XSS的区别ssrf常用协议ssrf防御php常用的伪协议常用函数XSS

    37分钟前
    00
  • 《分布式软总线:网络抖动下的数据传输“定海神针”》

    在当下,智能设备之间的互联互通已成为生活与工作的刚需。分布式软总线作为实现这一愿景的关键技术,正日益凸显其重要性。然而,网络环境的复杂性,尤其是网络抖动频繁的情况,给分布式软总线的数据传输带来了严峻挑战。如何在这种复杂多变的环境下维持稳定的

    29分钟前
    10
  • 《分布式软总线:不同频段Wi

    分布式软总线技术作为实现设备互联互通的关键,正逐渐成为构建万物互联世界的基石。然而,当分布式软总线面临不同频段Wi-Fi环境时,设备发现的兼容性问题成为了阻碍其广泛应用的一大挑战。这一问题不仅影响着用户体验,也制约着分布式软总线在智能家居、

    27分钟前
    00
  • MySQL中的锁机制

    一、按锁粒度分类行级锁(Row-Level Lock)共享锁(S Lock):允许事务读取一行,阻止其他事务获取相同行的排他锁。用法:SELECT ... FOR SHARE;(MySQL 8.0+,旧版用LOCK IN SHARE M

    22分钟前
    00
  • 那些程序员经典语录的深度解读

    1. “用代码行数衡量开发进度,就像用重量衡量飞机制造进度”——比尔·盖茨(Bill Gates)核心思想:强调质量与效率的辩证关系。代码行数仅是表面指标,真正的价值在于逻辑优化和功能实现。揭露了技术官僚主义对软件工程本质的误解。

    10分钟前
    00

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信