为什么Common Lisp没有通用运算符?

5
在CL中,我们有许多检查相等的运算符,这取决于数据类型: =string-equalchar=,然后是equaleql等等,对于其他数据类型也是如此,并且对于比较运算符也是一样的(编辑请不要忘记回答这些问题:)我们是否有通用的<>等?我们能使它们对另一个对象起作用吗?)
然而,该语言有使它们通用的机制,例如Practical Common Lisp中描述的generics (defgeneric, defmethod)。我想象得很好,同样的==运算符将适用于整数、字符串和字符,至少是这样!
已经朝着这个方向做了工作:https://common-lisp.net/project/cdr/document/8/cleqcmp.html 我认为这是初学者(包括我在内)的主要挫败感,甚至是一道难题,特别是我们来自其他语言(如Python),在那里我们使用一个等号运算符(==)进行每个相等性检查(通过对象帮助在自定义类型上实现)。

我今天读到一篇博客文章(不是单子教程,很棒的系列文章),指出了这一点。那个人转向Clojure,当然还有其他原因,那里只有一个(或两个?)运算符。

那么为什么会这样呢?有什么好的理由吗?我甚至找不到第三方库,甚至在CL21上也找不到。编辑:cl21当然有这种通用运算符。

在其他SO问题中,我看到了关于性能的讨论。首先,这不适用于我将写的小代码,所以我不在乎,如果你这样认为,你有数据来支持你的观点吗?

编辑:尽管回答的语气很强硬,但似乎没有;)我们在评论中讨论。


2
通用CLOS函数是在CL最初设计(82-84)几年后添加的。带有CLOS的变体在CLtL2(1990)中广泛发布,然后是ANSI CL。语言只进行了轻微更新。并不打算使Common Lisp完全面向对象。此外,相对较低级别函数的CLOS性能有些问题。Dylan类似于Scheme + CLOS - s表达式语法,它定义了更多基于通用函数的语言。 - Rainer Joswig
4个回答

6
肯特·皮特曼写了一篇有趣的文章来解决这个问题:The Best of intentions, EQUAL rights — and wrongs — in Lisp
此外,请注意,EQUAL确实适用于整数、字符串和字符。EQUALP还适用于列表、向量和哈希表等其他Common Lisp类型,但不适用于对象……根据某些定义,这些函数可能会“工作”。EQUALP页面末尾的注释对你的问题有一个好答案:

对象相等性不是一个具有唯一确定正确算法的概念。相等谓词的适当性只能在某个特定程序的需求背景下进行判断。虽然这些函数可以接受任何类型的参数,它们的名称听起来非常通用,但equal和equalp并不适用于每个应用程序。

特别要注意上面最后一个“works”定义中的技巧。

哦,好的提醒。但是它适用于列表、向量、哈希表和其他常见类型吗?我们能否为另一种类型定义它的相等性,比如 fset - Ehvince
@Ehvince 我编辑了关于列表、向量和哈希表的相等性测试的答案。虽然我理解对于更可扩展的 == 的渴望。 - kmkaplan
如果我理解这篇文章的话:有很多运算符是因为它们执行不同的操作,而且程序无法读取程序员的头脑(也是出于历史原因)。他通过使用 defgeneric 提出了新的动词(他没有对现有的动词进行子类化,它们不是基于 CLOS 的,因此不能像 Python 中那样可扩展)。在那里,默认情况下使用参数猜测类型,但也可以作为可选参数给出。这只是一个 POC,还需要编写一个包! - Ehvince
大部分是肯定的。我的阅读是,建议的新动词是一个defun,它是用defgeneric/defmethod实现的。一个令人眼前一亮的部分是,“当[一些程序员被]敦促编写自己的等式谓词以适应他们特定的需求时,他们有时会反应得好像我们在推脱他们一样,而不是意识到他们可以编写的任何函数和语言提供的任何函数一样有效。如果更改语言以适应这些错误报告,不同的用户可能会抱怨”。 - kmkaplan

5
一种新的库为标准Common Lisp函数添加了通用接口:https://github.com/alex-gutev/generic-cl/ GENERIC-CL为Common Lisp标准中的各种函数(如相等谓词和序列操作)提供了一个通用函数包装器。包装器的目标是提供一个标准接口,用于常见操作,例如测试两个对象是否相等,这对用户定义的类型是可扩展的。
它可以为相等性、比较、算术、对象、迭代器、序列、哈希表、数学函数等提供此功能。
因此,您可以自己定义+运算符。

4
我们有!eq适用于所有值,并且它始终有效,不受数据类型的影响。这正是您所需的。它就像Python中的is运算符。这一定是您正在寻找的东西吧?所有其他运算符都与eq同意,当其为t时,但它们往往对具有各种相似程度的完全不同的值为t
(defparameter *a* "this is a string")
(defparameter *b* *a*)
(defparameter *c* "this is a string")
(defparameter *d* "THIS IS A STRING")

所有这些都是等价的,因为它们包含相同的意思。equalp是所有equal函数中最宽松的。我认为2和2.0并不相同,但是equalp却认为它们相同。在我看来,2就是2,而2.0介于1.95和2.04之间,你看它们并不一样。
equal可以理解我的想法。 (equal * c * * d *)绝对是nil,这很好。然而,对于(equal * a * * c *),它返回t。两者都是字符数组,每个字符值相同,但两个字符串不是同一个对象,它们只是恰好看起来一样。
请注意,这里我对每一个值都使用了string。我们有4个equal函数,告诉您两个值是否有共同点,但只有eq告诉您它们是否相同。
这些都不是特定类型的。它们适用于所有类型,但它们并不是通用函数,因为它们早在语言添加了通用类型之前就存在了。也许你可以制作3-4个通用equal函数,但它们真的比我们已经有的那些更好吗?

2
好的回答!关键在于不同的等号运算符具有不同的语义,系统无法自动选择哪个是特定情况下正确的运算符(因为它不能读取程序员的思想来知道他的需求...)。 - Renzo
1
“EQ”对数字或字符的比较并不可靠,因为“Common Lisp不能保证当它的两个参数是相同的数字或字符时,“eq”是真的。” (http://www.lispworks.com/documentation/lw51/CLHS/Body/f_eq.htm). 你最好使用“EQL”。 - kmkaplan
@kmkaplan 这个问题涉及到泛型,eqlequal 更不具有泛型性,而且 equal 可以在所有 eql 会返回 t 的类型中使用。如果你读了我的回答,你就知道 (eq 1 1) 可能不会做你想要的事情,因为它们实际上是看起来相同但是不同的对象(在某些实现中,而不是所有实现)。 - Sylwester
看起来有一个相等运算符可以做很多事情。那么比较运算符呢,比如 >?我们是否有一个通用的比较运算符,可以适用于数字、字符串、字符、序列、集合等其他数据类型,并且我们能让它们适用于我们自己的对象吗? - Ehvince
对我来说,关于eq在数字和字符上的最令人烦恼的部分是(let ((x 0)) (eq x x))的结果是不可预测的。 - kmkaplan
显示剩余5条评论

0

幸运的是,CL21 引入了更通用的操作符,特别是对于序列,它定义了 lengthappendsetffirstrestsubseqreplacetakedropfilltake-whiledrop-whilelastbutlastfind-ifsearchremove-ifdelete-ifreversereducesortsplitjoinremove-duplicateseverysomemapsum(还有更多)。不幸的是,文档并不是很好,最好看一下 源代码。这些至少适用于字符串列表向量,并定义了新的 abstract-sequence 的方法。

另请参阅


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接