为什么用户定义的运算符不更常见?


94

我从功能语言中错过的一个特性是,运算符只是函数,因此添加自定义运算符通常与添加函数一样简单。许多过程语言都允许运算符重载,因此从某种意义上说,运算符仍然是函数(在D中,该运算符在模板参数中作为字符串传递,这是非常正确的)。

似乎在允许运算符重载的地方,添加额外的自定义运算符通常很简单。我找到了这篇博客文章,该文章认为由于优先级规则,自定义运算符不能很好地使用后缀表示法,但是作者为该问题提供了几种解决方案。

我环顾四周,找不到支持该语言中的自定义运算符的任何过程语言。有一些技巧(例如C ++中的宏),但这与语言支持几乎不一样。

由于此功能实施起来很简单,为什么它不更常见?

我知道这可能会导致某些丑陋的代码,但是这并没有阻止语言设计人员过去添加易于使用的有用功能(宏,三元运算符,不安全的指针)。

实际用例:

  • 实现缺少的运算符(例如,Lua没有按位运算符)
  • 模拟D ~(数组串联)
  • DSL
  • 使用|如UNIX管道风格的语法糖(使用协程/发电机)

我对确实允许自定义运算符的语言也很感兴趣,但是我对为什么它被排除在外更感兴趣。我曾考虑过分叉一种脚本语言来添加用户定义的运算符,但是当我意识到自己没在任何地方看到它时就停下来了,所以可能有充分的理由为什么比我更聪明的语言设计师不允许这样做。


4
有一个reddit的讨论正在进行这个问题。
2012年

2
@dimatura:我对R不太了解,但是自定义运算符似乎仍然不太灵活(例如,没有适当的方法来定义固定性,范围,重载等),这说明了为什么它们使用不多。在其他语言中,这是不同的,当然在Haskell中,该语言大量使用自定义中缀运算符。具有合理的自定义中缀支持的过程语言的另一个示例是Nimrod,当然Perl也允许它们
大约

4
还有另一个选择,Lisp实际上在运算符和函数之间没有区别。
BeardedO 2012

4
尚未提及Prolog,这是另一种语言,其中运算符只是函数的语法糖(甚至是数学运算符),它允许您定义具有自定义优先级的自定义运算符。
David Cowden 2013年

2
@ BeardedO,Lisp中根本没有infix运算符。一旦介绍了它们,就必须优先处理所有此类问题。
SK-logic

Answers:


134

在编程语言设计中有两种截然相反的思想流派。一种是程序员在较少的限制下编写更好的代码,另一种是他们在更多的限制下编写更好的代码。在我看来,现实是,经验丰富的程序员会以较少的限制蓬勃发展,但是限制可以使初学者的代码质量受益。

用户定义的操作员可以在经验丰富的人员手中编写出非常精美的代码,而对于初学者来说则是非常糟糕的代码。因此,您的语言是否包括它们取决于您的语言设计师的思想流派。


23
关于这两种思想流派,plus.google.com/110981030061712822816/posts/KaSKeg4vQtz也+1,因为他们对语言设计者为什么选择一个而另一个选择了最直接(也可能最正确)的答案。我还要说的是,根据您的论点,您可以推断出,允许使用的语言较少,因为开发人员中的一小部分比其他人具有很高的技能(按定义,任何事物的最高百分比将始终是少数)
Jimmy Hoffa,2012年

12
我认为这个答案含糊不清,与这个问题仅是微不足道的。(我也恰好认为您的描述是错误的,因为它与我的经验相矛盾,但这并不重要。)
Konrad Rudolph

1
正如@KonradRudolph所说,这并不能真正回答问题。采用Prolog之类的语言,它可以让您对运算符进行任何操作,包括在插入中缀或前缀时定义其优先级。我认为您可以编写自定义运算符与目标受众的技能水平没有任何关系,而是因为Prolog的目标是尽可能逻辑地阅读。包含自定义运算符使您可以编写逻辑上读取的程序(毕竟,序言程序只是一堆逻辑语句)。
David Cowden

我怎么想念那个斯蒂维·兰特?噢,有个书签
George Mauer 2013年

向一个理念我会下降,如果我认为程序员是最有可能写出最好的代码,如果语言给他们的工具,说编译器应该使各种推断(比如自动装箱参数的Format方法),当它应该拒绝(例如,对的自动装箱参数ReferenceEquals。语言能力越强,程序员可以说出何时某些推论是不合适的,那么在适当的时候,它越安全地提供方便的推论。
supercat 2014年

83

给定在使用〜或“ myArray.Concat(secondArray)”连接数组之间的选择,我可能更喜欢后者。为什么?因为〜是一个完全无意义的字符,仅具有其含义-数组串联的含义-在编写该字符的特定项目中给出。

正如您所说,基本上,运算符与方法没有什么不同。但是,尽管可以为方法赋予可读性,易懂的名称,从而加深了对代码流的理解,但运算符却是不透明且处境特殊的。

这就是为什么我也不喜欢PHP的.运算符(字符串连接)或Haskell或OCaml中的大多数运算符的原因,尽管在这种情况下,一些针对功能语言的通用标准正在出现。


27
所有运营商都可以这样说。是什么使它们良好,也可以使用户定义的运算符(如果明智地定义了)也变得如此好:尽管它们所使用的字符只是一个字符,但广泛使用和可能的助记符属性立即在源代码中使其含义对于熟悉它的人来说很明显。因此,您目前的答案并不令人信服。

33
正如我在最后提到的那样,某些运算符具有通用识别性(例如标准的算数符号,按位移位,AND / OR等),因此其简洁性超过了其不透明性。但是,能够定义任意运算符似乎使这两个方面都变得最糟。
Avner Shahar-Kashtan

11
因此,您也赞成禁止在语言级别使用单字母名称吗?更笼统地说,是不是不允许使用任何看起来弊大于利的语言?这是一种有效的语言设计方法,但不是唯一的方法,因此我认为您不能以它为前提。

9
我不同意自定义运算符正在“添加”功能,它消除了限制。运算符通常只是函数,因此可以使用动态的,上下文相关的符号代替运算符的静态符号表。我想这就是操作符重载的处理方式,因为+并且<<肯定没有定义Object(在C ++的裸类上执行此操作时,我得到“在...中不匹配operator + in ...”)。
beatgammit 2012年

10
需要很长时间才能弄清楚,编码通常不是要让计算机为您做某事,而是要生成一个人和计算机都可以阅读的文档,而人比计算机要重要得多。这个答案是完全正确的,如果下一个人(或您在2年后)甚至要花10秒来试图找出〜的含义,那么您可能还应该花10秒来键入方法调用。
Bill K

71

由于此功能实施起来很简单,为什么它不更常见?

你的前提是错的。这不是 “实施起来很简单”。实际上,它带来了很多问题。

让我们看看帖子中建议的“解决方案”:

  • 没有优先权。作者本人说:“不使用优先规则根本不是一种选择。”
  • 语义感知解析。如文章所述,这将要求编译器具有很多语义知识。这篇文章实际上并没有提供解决方案,我告诉你,这根本不是一件小事。编译器的设计是在功耗和复杂性之间进行权衡。特别地,作者提到了收集相关信息的预解析步骤,但是预解析效率低下,编译器非常努力地使解析过程最小化。
  • 没有自定义的中缀运算符。好吧,这不是解决方案。
  • 混合解决方案。该解决方案具有很多(但不是全部)语义感知解析的缺点。特别是,由于编译器必须将未知标记视为可能代表自定义运算符,因此它通常无法产生有意义的错误消息。还可能需要所述运算符的定义来进行解析(以收集类型信息等),再次需要额外的解析过程。

总而言之,无论是从解析器的复杂性还是从性能方面来说,这都是一项昂贵的实现功能,并且尚不清楚它将带来很多好处。当然,定义新运算符的功能有一些好处,但即使是那些有争议的(只要看看其他答案,争论拥有新运算符也不是一件好事)。


2
感谢您理智的声音。我的经验表明,认为琐碎琐事的人要么是专家,要么是极度愚昧无知,并且在后一类别中更常见:x
Matthieu M.

14
这些问题中的每一个问题都已经用存在20年的语言解决了……
Philip JF

11
但是,实现起来非常简单。忘掉所有龙书解析算法,这是21世纪,是时候继续前进了。甚至扩展语言语法本身也很容易,并且添加给定优先级的运算符很简单。看看Haskell解析器。对于“主流”肿的语言而言,它比解析器要简单得多。
SK-logic

3
@ SK-logic您的笼统断言并没有说服我。是的,解析已经开始。不,根据类型实现任意运算符优先级并非“琐事”。在有限的上下文中生成良好的错误消息并非“轻而易举”。用需要多次通过才能转换的语言来生产高效的解析器是不可行的。
Konrad Rudolph 2013年

6
在Haskell中,解析是“容易的”(与大多数语言相比)。优先级与上下文无关。给您带来严重错误消息的部分与类型类和高级类型系统功能有关,而不与用户定义的运算符有关。这是一个难题,而不是用户定义,这是重载。
菲利普·JF

25

现在让我们忽略整个“操作员被滥用以损害可读性”的论点,而将注意力集中在语言设计的含义上。

中缀运算符比简单的优先级规则有更多的问题(尽管直率地说,您引用的链接使该设计决策的影响微不足道)。一种是解决冲突:定义a.operator+(b)和时会发生什么b.operator+(a)?一个优先于另一个会导致破坏该算子的预期交换性质。引发错误可能导致原本可以正常工作的模块一起损坏。当您开始将派生类型投入混合时会发生什么?

事实是,运算符不仅仅是功能。函数可以是独立的,也可以由其类拥有,这些函数明确提供了拥有多态调度的参数(如果有)。

这就忽略了操作员引起的各种包装和分辨率问题。语言设计人员(大体上)限制后缀运算符定义的原因是因为它在提供可争议的好处的同时,给语言带来了很多问题。

坦率地说,因为实施起来并不容易。


1
我相信这就是Java不包含它们的原因,但是包含它们的语言对于优先级有明确的规则。我认为这类似于矩阵乘法。它不是可交换的,因此在使用矩阵时必须要知道。我认为在处理类时也是如此,但是是的,我确实同意非交换+是邪恶的。但这是否真的反对用户定义的运算符?似乎在一般情况下反对运算符重载。
beatgammit

1
@tjameson-是的,语言确实对数学和优先级有明确的规定。如果运算符因诸如之类的操作而超载,则数学运算的优先级规则可能不会真正适用boost::spirit。一旦允许用户定义的运算符,情况就会恶化,因为没有很好的方法甚至可以很好地定义数学优先级。我在一种语言的背景下写了一些关于自己的文章,该语言专门用于解决任意定义的运算符的问题。
Telastyn 2012年

22
先生,我叫废话。OO贫民窟之外有生命,并且函数不一定属于任何对象,因此找到合适的函数与找到合适的运算符完全相同。
Matthieu M.

1
@matthieuM。-当然可以 在不支持成员功能的语言中,所有权和调度规则更加统一。但是到了那时,原始问题变成了“为什么非OO语言不是更普遍?” 这是另外一回事。
Telastyn

11
您是否看过Haskell定制运算符的方式?它们的工作原理与正常功能完全相同,不同之处在于它们还具有关联的优先级。(实际上,普通函数也是如此,即使它们在那里也没有真正的区别。)基本上,默认情况下,运算符为infix,名称为前缀,但这是唯一的区别。
Tikhon Jelvis

19

我想你会惊奇地发现往往运算符重载实现某种形式。但是它们在许多社区中并不常用。

为什么使用〜连接到数组?为什么不像Ruby那样使用<<?因为您使用的程序员可能不是Ru​​by程序员。还是D程序员。那么,当他们遇到您的代码时,他们会怎么做?他们必须去查找该符号的含义。

我曾经和一个非常出色的C#开发人员一起工作,他也对函数式语言有所了解。突然,他开始通过扩展方法和使用标准monad术语将monad 引入C#。一旦您知道它的含义,就不会有人质疑他的某些代码更简洁,甚至更具可读性,但这确实意味着每个人都必须先学习monad术语,然后才能理解代码

足够公平,您认为呢?这只是一个小团队。我个人不同意。每个新开发人员注定会对此术语感到困惑。我们在学习新领域方面没有足够的问题吗?

在另一方面,我会愉快地使用??运营商在C#中,因为我希望其他C#开发人员知道它是什么,但我不会超载成不默认支持的语言。


我不理解您的Double.NaN示例,此行为是浮点规范的一部分,并且在我使用的每种语言中都受支持。您是在说不支持自定义运算符,因为它会使不知道该功能的开发人员感到困惑吗?这听起来像是反对使用三元运算符或您的??示例的相同论点。
Beatgammit

@tjameson:没错,实际上,这与我要提出的观点有点相切,而且写得还不是特别好。我想到了?? 我写那个例子时比较喜欢这个例子。删除double.NaN段落。
pdr

6
我认为您的“每个新开发人员注定会被此术语所迷惑”一点都显得乏善可陈,对我来说,担心新开发人员对代码库的学习曲线与担心新开发人员对代码库的学习曲线一样.NET的新版本。我认为更重要的是,当开发人员学习它(新的.NET或您的代码库)时,它有意义并显示出价值吗?如果是这样,那么学习曲线是值得的,因为每个开发人员只需要学习一次,尽管代码需要处理无数次,因此,如果对代码进行改进,则花费很小。
Jimmy Hoffa

7
@JimmyHoffa这是经常性费用。为每位新开发人员支付预付款,并影响现有开发人员,因为他们必须提供支持。同时还有文档成本和风险元素-今天感觉足够安全,但是30年来,每个人都将继续前进,语言和应用程序现在都是“旧版”,文档是一大堆蒸蒸日上的东西那些懒得不能输入“ .concat()”的程序员的聪明才智会给他留下一些可怜的傻瓜。期望值是否足以抵消成本?
Sylverdrag

3
同样,“每个新开发者注定会被这种术语所混淆。我们在学习新领域时是否没有足够的问题?” 可能早在80年代就已经应用于OOP,之前的结构化编程或10年前的IoC。现在该停止使用这种谬​​论了。
Mauricio Scheffer 2014年

11

我可以想到一些原因:

  • 它们的实现并非易事 -允许任意的自定义运算符会使您的编译器更加复杂,特别是如果您允许用户定义优先级,固定性和Arity规则。如果简单是一种优点,那么操作员重载将使您脱离良好的语言设计。
  • 它们被滥用 -主要是因为编码人员认为重新定义运算符并开始为各种自定义类重新定义它们是“很酷的”。不久之后,您的代码中到处都是大量的自定义符号,其他任何人都无法阅读或理解,因为操作员没有遵循容易理解的常规规则。我不赞成使用“ DSL”参数,除非您的DSL恰好是数学的一部分:-)
  • 它们损害了可读性和可维护性 -如果定期重写操作员,使用该工具时可能很难发现,并且编码人员被迫不断问自己操作员在做什么。给出有意义的函数名称要好得多。输入一些额外的字符很便宜,长期的维护问题很昂贵。
  • 它们可以打破隐含的性能期望。例如,我通常希望在数组中查找元素为O(1)。但是随着运算符的重载,根据索引运算符的实现,someobject[i]很容易进行O(n)运算。

实际上,与仅使用常规函数相比,很少有运算符重载具有合理用途。一个合理的例子可能是设计一个供数学家使用的复数类,这些数学家理解为复数定义数学运算符的众所周知的方法。但这确实不是很常见的情况。

需要考虑的一些有趣案例:

  • Lisps:通常根本不区分运算符和函数- +只是常规函数。您可以定义自己喜欢的函数(通常有一种在单独的命名空间中定义它们的方法,以避免与内置冲突+),包括运算符。但是在文化上倾向于使用有意义的函数名称,因此不会被滥用。另外,在Lisp中,前缀表示法倾向于专门使用,因此,运算符重载提供的“语法糖”中的值较小。
  • Java的 -不允许操作符重载。有时候这很烦人(对于像复数情况这样的东西),但是平均而言,这可能是Java的正确设计决定,它旨在用作一种简单的通用OOP语言。由于这种简单性,Java代码实际上对于中/低技能的开发人员来说很容易维护。
  • C ++具有非常复杂的运算符重载。有时这会被滥用(cout << "Hello World!"有人吗?),但考虑到C ++作为一种复杂语言的定位,这种方法是有意义的,该语言可以进行高级编程,同时仍然使您与性能非常接近,因此您可以编写一个行为复杂的数字类。完全符合您的要求,而不会影响性能。据了解,用脚射击自己是您的责任。

8

由于此功能实施起来很简单,为什么它不更常见?

是不容易实现(除非平凡的实现)。即使以理想的方式实现,它也不会给您带来太多好处:简洁带来的可读性提高被陌生和不透明带来的可读性损失所抵消。简而言之,这很罕见,因为通常不值得开发人员或用户花费时间。

就是说,我可以想到三种语言可以做到这一点,并且它们以不同的方式做到这一点:

  • Racket是一种方案,当它并非全部由S-expression-y允许时,它期望您为要扩展的任何语法编写相当于解析器的内容(并提供有用的钩子以使其易于处理)。
  • Haskell是一种纯函数式编程语言,它允许定义仅由标点符号组成的任何运算符,并允许您提供固定性级别(可用10个级别)和关联性。三元运算符等可以由二元运算符和高阶函数创建。
  • 阿格达,一个依赖类型编程语言,是与运营商(纸非常灵活这里)允许两个IF-THEN和IF-THEN-ELSE将其定义为在同一个程序的运营商,但它的词法,语法分析器和评估都强耦合结果是。

4
您已经说过它“不平凡”,并立即列出了三种语言,这些语言具有一些异常琐碎的实现。所以,毕竟这是很琐碎的,不是吗?
SK-logic

7

不鼓励自定义运算符的主要原因之一是因为任何运算符都可以表示/可以做任何事情。

例如cstream,广受批评的左移过载。

当一种语言允许操作员重载时,通常会鼓励使操作员行为与基本行为保持相似,以避免混淆。

此外,用户定义的运算符使解析更加困难,尤其是在还存在自定义首选项规则的情况下。


6
我看不到有关运算符重载的部分如何应用于定义全新的运算符。

我已经看到很好地使用了运算符重载(线性代数库),但是正如您提到的那样,它非常糟糕。我认为这不是反对自定义运算符的好理由。解析可能是一个问题,但是当期望有运算符时,这很明显。解析之后,由代码生成器查找运算符的含义。
Beatgammit

3
这与方法重载有何不同?
约尔格W¯¯米塔格

@JörgWMittag带有方法重载(大部分时间)都附加了一个有意义的名称。带有符号很难更简洁地解释会发生什么
棘手怪胎

1
@ratchetfreak:好吧。+将两件事相加,-相减,*相乘。我的感觉是,没有人强迫程序员使函数/方法add实际添加任何东西,并且doNothing可能启动核武器。而且a.plus(b.minus(c.times(d)).times(e)其可读性要差得多a + (b - c * d) * e(额外的好处-首先是转录错误)。我看不出首先有什么更有意义的...
Maciej Piechotka

4

我们不使用用户定义的运算符,原因与我们不使用用户定义的字相同。没有人会称其功能为“ sworp”。向他人传达您的想法的唯一方法是使用共享语言。这意味着您要为其编写代码的社会必须同时知道单词和符号(操作员)。

因此,您在编程语言中使用的运算符就是我们在学校教过的(算术)运算符,或者是在编程社区中建立的运算符,例如布尔运算符。


1
此外,还可以发现功能背后的含义。对于“ sworp”,您可以很容易地将其粘贴到搜索框中并找到其文档。将操作员纳入搜索引擎是一个完全不同的球场。我们当前的搜索引擎只是在寻找运算符,而在某些情况下,我浪费了大量时间来寻找含义。
Mark H

1
您算多少“学校”?例如,我认为∈for elem是一个好主意,当然每个人都应该理解一个运算符,但其他人似乎不同意。
Tikhon Jelvis

1
这不是符号的问题。这是键盘的问题。例如,我使用启用了Unicode美化程序的emacs haskell-mode。并且它会自动将ascii运算符转换为unicode符号。但是,如果没有ascii等效项,我将无法键入。
Vagif Verdi

2
自然语言仅由“用户定义的单词”构成,而没有其他内容。实际上,某些语言确实鼓励自定义单词的形成。
SK-logic

4

至于确实支持这种重载的语言:Scala可以,实际上C ++可以用一种更干净,更好的方式。大多数字符都可以在函数名称中使用,因此您可以根据需要定义!+ * = ++之类的运算符。内置了对infix的支持(所有带有一个参数的函数)。我认为您也可以定义此类功能的关联性。但是,您不能定义优先级(仅使用丑陋的技巧,请参阅此处)。


4

尚未提及的一件事是Smalltalk的情况,其中所有内容(包括运算符)都是消息发送。“运营商”之类+|等等其实都是一元的方法。

所有方法都可以重写,因此a + b如果ab都是整数,则意味着整数加法;如果它们都是OrderedCollections ,则意味着向量加法。

没有优先级规则,因为这些只是方法调用。这对标准数学符号具有重要意义:3 + 4 * 5平均值(3 + 4) * 5,而不是3 + (4 * 5)

(这是Smalltalk新手的主要绊脚石。违反数学规则将删除一个特例,以便所有代码评估从左到右统一进行,从而使该语言更加简单。)


3

您在这里与两件事作斗争:

  1. 为什么运算符首先存在于语言中?
  2. 操作员在功能/方法上的优势是什么?

在大多数语言中,运算符并没有真正实现为简单功能。它们可能具有一些功能支架,但是编译器/运行时明确意识到其语义含义以及如何将其有效地转换为机器代码。即使与内置函数相比,这也更加正确(这就是为什么大多数实现在其实现中也不包括所有函数调用开销的原因)。大多数运算符是对CPU中原始指令的更高级别的抽象(这就是为什么大多数运算符是算术,布尔或按位运算的部分原因)。您可以将它们建模为“特殊”功能(称它们为“原始”或“内建”或“本地”或其他),但是要做到这一点通常需要一组非常强大的语义来定义此类特殊功能。另一种选择是使内置运算符在语义上看起来像用户定义的运算符,但否则会在编译器中调用特殊路径。这与第二个问题的答案不符。

除了我上面提到的机器翻译问题外,在语法层面上,运算符与功能并没有什么不同。它们与众不同的特征往往是简洁和象征性,这暗示着它们必须有用的一个重要的附加特征:它们必须对开发人员具有广泛理解的含义/语义。短符号不会传达太多含义,除非它是已经理解的一组语义的简写形式。这使用户定义的操作员本质上无济于事,因为就其本质而言,他们并没有得到如此广泛的理解。它们与一个或两个字母函数名称一样有意义。

C ++的运算符重载为对此进行检查提供了沃土。大多数运算符重载“滥用”都以重载的形式出现,这些重载破坏了一些广为人知的语义协定(经典示例是运算符+的重载,如a + b!= b + a,或者其中+修改了其中一个操作数)。

如果您看一下Smalltalk,它确实允许操作符重载用户定义的操作符,那么您会看到某种语言可能会做些什么,以及它会多么有用。在Smalltalk中,运算符只是具有不同语法属性的方法(即,它们被编码为中缀二进制)。该语言对特殊的加速运算符和方法使用“原始方法”。您发现几乎没有创建任何用户定义的运算符,而且当它们创建时,它们的使用率往往不如作者可能打算使用的那么多。甚至等同于运算符重载的情况也很少见,因为将新函数定义为运算符而不是方法主要是净损失,因为后者允许表达函数语义。


1

我一直发现C ++中的运算符重载对于单个开发人员团队来说是一个便捷的快捷方式,但是从长远来看,这会引起各种混乱,这仅仅是因为方法调用以不容易的方式被“隐藏”了为了使诸如doxygen之类的工具脱颖而出,人们需要了解这些习语才能正确使用它们。

有时,要理解甚至比您期望的要难得多。曾几何时,在一个大型的跨平台C ++项目中,我认为通过创建一个FilePath对象(类似于Java的File对象)来规范构建路径的方式是一个好主意,该对象将具有operator /用于连接另一个对象路径部分(因此您可以做类似的事情,File::getHomeDir()/"foo"/"bar"并且它将在我们所有支持的平台上做正确的事情)。看到它的每个人都会说:“到底是什么?字符串分割?……哦,这很可爱,但是我不相信它会做正确的事情。”

同样,在图形编程或其他领域中,向量/矩阵数学经常发生的许多情况很容易发生,例如Matrix * Matrix,Vector * Vector(点),Vector%Vector(叉),Vector%Vector(叉),Matrix * Vector(矩阵变换),矩阵^向量(忽略均匀坐标的特殊情况的矩阵变换-对表面法线很有用)等,但是虽然为编写向量数学库的人节省了一些解析时间,但它只结束了让其他人更加困惑这个问题。只是不值得。


0

运算符重载是一个坏主意,原因与方法重载是一个坏主意的原因相同:屏幕上的相同符号根据其周围的内容而具有不同的含义。这使得休闲阅读更加困难。

由于可读性是可维护性的关键方面,因此应始终避免过载(在某些非常特殊的情况下除外)。每个符号(无论是运算符还是字母数字标识符)最好具有自己站立的独特含义。

举例说明:在读取不熟悉的代码时,如果遇到不知道的新字母数字标识符,至少可以得到一个好处,即您知道不知道它。然后,您可以查找它。但是,如果看到一个知道其含义的通用标识符或运算符,则您不太可能注意到它实际上已经被重载以具有完全不同的含义。要知道哪些运算符已被重载(在广泛使用了重载的代码库中),即使您只想阅读其中的一小部分,也需要掌握完整代码的使用知识。这将使新开发人员无法加快该代码的开发速度,也将使人们无法从事少量工作。这可能对程序员的工作安全性有好处,但是如果您对代码库的成功负责,则应不惜一切代价避免这种做法。

因为运算符的大小很小,所以重载运算符将允许使用更密集的代码,但是使代码密集化并不是真正的好处。具有两倍逻辑的行需要两倍的读取时间。编译器不在乎。唯一的问题是人类可读性。由于使代码紧凑不会提高可读性,因此紧凑性并没有真正的好处。继续并占用空间,为唯一的操作赋予唯一的标识符,从长远来看,您的代码将更加成功。


“屏幕上的相同符号根据周围的含义而具有不同的含义”-大多数语言的许多操作员都已经遇到了这种情况。
rkj

-1

我认为在处理优先级和复杂的解析方面存在技术难题,我认为必须考虑编程语言的一些方面。

运算符通常是简短的逻辑结构,使用核心语言(比较,赋值..)进行了很好的定义和记录。如果没有文档(例如与之比较)a^b,它们通常也很难理解xor(a,b)。在正常编程中,实际上可能有意义的操作符数量非常有限(>,<,=,+等)。

我的想法是,最好坚持使用一种语言定义的一组运算符-然后允许运算符重载这些运算符(给出软性建议,即运算符应执行相同的操作,但要使用自定义数据类型)。

简单的运算符重载(C#,C ++等)实际上可以实现~和的使用情况|。DSL是有效的使用区域,但可能是唯一有效的区域之一(从我的角度来看)。但是,我认为有更好的工具可以在其中创建新语言。通过使用任何一种编译器工具,在另一种语言中执行真正的DSL语言并不困难。“扩展LUA参数”也是如此。一种语言很可能主要是为了以特定方式解决问题而定义的,而不是作为子语言的基础(存在例外)。


-1

另一个因素是,使用可用的运算符定义操作并不总是那么简单。我的意思是,是的,对于任何类型的数字,“ *”运算符都是有意义的,并且通常以语言或现有模块来实现。但是,对于需要定义的典型复杂类(例如ShipingAddress,WindowManager,ObjectDimensions,PlayerCharacter等),其行为尚不清楚...向地址添加或减去数字是什么意思?将两个地址相乘?

当然,您可以定义将字符串添加到ShippingAddress类意味着一个自定义操作,例如“在地址中替换第1行”(而不是“ setLine1”函数),并添加数字是“替换邮政编码”(而不是“ setZipCode”) ,但是代码不太易读和令人困惑。我们通常认为运算符被用在基本类型/类中,因为它们的行为是直观,清晰和一致的(至少一旦您熟悉该语言)。考虑类型,例如Integer,String,ComplexNumbers等。

因此,即使在某些特定情况下定义运算符可能非常有用,它们的实际实现也非常有限,因为在基本语言包中已经实现了99%的明显优势。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.