操作员比关键字或函数更易读吗?[关闭]


14

这有点主观,但是我希望能更清楚地了解哪些因素使操作员易于使用,比较钝和困难。我最近一直在考虑语言设计,而我经常回想的一个问题是何时使用该语言对该键进行一些关键操作,以及何时使用关键字或函数。

Haskell为此而臭名昭著,因为自定义运算符易于创建,并且经常会打包新的数据类型,并附带多个运算符以供使用。例如,Parsec库带有大量运算符,用于将解析器组合在一起,并带有类似的宝石>..> 我什至不记得它们现在的含义,但是我确实记得一旦记住了它们,它们将非常易于使用他们的意思是。这样的函数调用会leftCompose(parser1, parser2)更好吗?当然更冗长,但在某些方面更加清晰。

类似C的语言中的运算符重载是一个类似的问题,但又与另一个问题相混淆,即重载了熟悉的运算符的含义,例如+具有异常的新含义。

在任何新语言中,这似乎都是一个棘手的问题。例如,在F#中,强制转换使用数学派生的类型转换运算符,而不是C#样式强制转换语法或冗长的VB样式。C#:(int32) xVB:CType(x, int32)F#:x :> int32

从理论上讲,一种新语言可以为大多数内置功能提供运算符。取而代之的defdecvar变量声明,为什么不! name@ name或类似的东西。它肯定会缩短声明后再进行绑定:@x := 5代替declare x = 5let x = 5 大多数代码将需要大量变量定义,那么为什么不呢?

操作员什么时候清楚和有用,什么时候不清楚?



1
我希望曾经向我抱怨Java缺少运算符重载的1,000名开发人员中的一位能够回答这个问题。
smp7d 2012年

1
我在输入问题时就想到了APL,但是在某种程度上可以阐明要点,并将其转移到不太主观的领域。我认为大多数人都可以同意APL通过其数量过多的操作员而失去了易用性,但是当语言没有操作员重载时,抱怨的人肯定会赞成某些功能非常适合充当运营商。在禁止的自定义运算符之间,除运算符外,必须隐藏一些实际实现和使用的准则。
CodexArcanum 2012年

如我所见,缺少运算符重载是令人反感的,因为它使本机类型优先于用户定义的类型(请注意,Java也以其他方式执行此操作)。对于Haskell风格的自定义运算符,我的看法更加矛盾,这似乎是一个麻烦的公开邀请……
即将

2
@comingstorm:我实际上认为Haskell方法更好。当您有一组有限的运算符可以以不同的方式重载时,通常会被迫在不同的上下文中重用运算符(例如,+用于字符串连接或<<用于流)。另一方面,在Haskell中,运算符要么只是具有自定义名称(即不重载)的函数,要么是类型类的一部分,这意味着尽管它是多态的,但它对每种类型都执行相同的逻辑操作,并且甚至具有相同的类型签名。因此,>>>>每一个类型,是永远不会成为一个移位。
Tikhon Jelvis'3

Answers:


16

从一般语言设计的角度来看,函数和运算符之间没有任何区别。可以将函数描述为带有任何(甚至变量)变量的前缀操作。关键字可以看作是具有保留名称的函数或运算符(这在设计语法时很有用)。

所有这些决定最终都取决于您希望该符号如何阅读以及您所说的是主观的,尽管可以做出一些显而易见的合理化,例如,使用众所周知的infix运算符进行数学运算,因为大家都知道它们

最后,Dijkstra 他使用的数学符号写了一个有趣的论据,其中包括对前缀和前缀符号的权衡取舍的很好讨论。


4
我想再给您+1,作为Dijkstra论文的链接。
AProgrammer 2012年

很棒的链接,您必须发布一个PayPal帐户!;)
Adriano Repetti 2012年

8

对我来说,当您无法再大声或脑海中读取代码行并且变得有意义时,操作员将不再有用。

例如,declare x = 5reads "declare x equals 5"或as let x = 5可以读为"let x equal 5",这在大声读出时是很容易理解的,但是读数@x := 5被读为"at x colon equals 5"(或者"at x is defined to be 5"如果您是数学家),这根本没有意义。

因此,我认为,如果可以由不熟悉该语言的相当聪明的人朗读并理解代码,请使用运算符,否则请使用函数名。


5
我认为@x := 5很难弄清楚-我还是大声念自己set x to be equal to 5
FrustratedWithFormsDesigner 2012年

3
在这种情况下,@ Rachel在:=编程语言之外的数学符号中具有更广泛的用途。同样,类似的陈述也x = x + 1变得荒谬。
ccoakley 2012年

3
具有讽刺意味的是,我忘记了有些人不会承认这:=是作业。实际上有几种语言会使用它。我认为Smalltalk可能是最著名的。赋值和相等性是否应该是单独的运算符,完全是蠕虫的完全不同的话题,一个蠕虫可能已经被另一个问题覆盖了。
CodexArcanum 2012年

1
@ccoakley谢谢,我不知道它:=是用于数学的。我为它找到的一个定义是“被定义为”,因此@x := 5将其翻译成英语"at x is defined to be 5",对我而言仍然没有应有的意义。
雷切尔

1
当然,Pascal使用:=作为赋值,因为ASCII中缺少左箭头。
Vatine 2012年

4

熟悉操作员时,操作员会清楚而有用。这可能意味着,仅当已选择的操作符(如形式,优先级和关联性)足够接近时才应执行重载操作符,这是已经建立的惯例。

但是进一步研究,有两个方面。

语法(运算符的前缀,而不是函数调用语法的前缀)和命名。

对于语法,还有另一种情况,即中缀比前缀足够清楚,以至于可以保证您变得熟悉:在调用被链接和嵌套时。

a * b + c * d + e * f+(+(*(a, b), *(c, d)), *(e, f))或进行比较+ + * a b * c d * e f(两者都可以在与中缀版本相同的条件下进行解析)。+在我看来,将术语分开而不是在其中一个词前加一个很长的距离这一事实使它在我眼中更具可读性(但您必须记住前缀语法不需要的优先规则)。如果您需要以这种方式复合事物,则长期受益的方法值得学习。即使您使用字母而不是使用符号来命名您的运营商,您仍然可以保持这一优势。

关于运算符的命名,我发现除了使用已建立的符号或明确的名称以外,使用其他方法没有什么好处。是的,它们可能更短一些,但是它们确实很神秘,如果您没有充分的理由知道它们,它们将很快被忘记。

如果您使用语言设计POV,则第三方面是,应该打开还是关闭运算符集-是否可以添加运算符-以及优先级和关联性是否应由用户指定。我的第一步是要小心,不要提供用户可指定的优先级和关联性,而我会更开放地使用开放集(这将增加使用运算符重载的推动力,但会减少使用不良运算符的推动力只是从infix语法中受益)。


如果用户可以添加运算符,为什么不让他们设置他们添加的运算符的优先级和关联性?
Tikhon Jelvis'3

@TikhonJelvis,这主要是保守性,但是如果您希望能够将其用于示例以外的其他方面,则需要考虑一些方面。例如,您想将优先级和关联性绑定到运算符,而不是重载集合的给定成员(解析后选择成员,选择不会影响解析)。因此,要使用同一运算符集成库,它们必须就其优先级和关联性达成一致。在添加可能性之前,我将尝试找出为什么这么少的语言遵循Algol 68(没有AFAIK)并允许对其进行定义。
AProgrammer 2012年

好了,Haskell让您定义任意运算符并设置它们的优先级和关联性。不同之处在于您本身不能重载运算符- 它们的行为类似于正常函数。这意味着您不能有不同的库尝试将同一运算符用于不同的事物。由于您可以定义自己的运算符,因此没有太多理由将相同的运算符重用于不同的事情。
Tikhon Jelvis'3

1

直观地理解“以符号表示操作符”很有用。 +-明显的加法和减法,例如,这就是为什么几乎每一个语言永远把它们作为自己的运营商。同样的,比较(<>被教小学的时候,和<=>=基本都是比较直观的扩展,当你明白,有标准键盘上没有下划线,比较关键。)

*并且/不太明显,但是它们已被普遍使用,并且它们在数字小键盘上与+-键相邻的位置有助于提供上下文。C <<和和>>属于同一类别:当您了解什么是左移和右移时,理解箭头后面的助记符并不难。

然后,您会发现一些真正奇怪的东西,它们会将C代码变成难以理解的运算符。(在这里选择C是因为它是一个非常重运算符的示例,通过使用C或后代语言,每个人都非常熟悉其语法。)例如,然后是%operator。每个人都知道那是什么:这是一个百分号...对吗?除C外,它与100除法无关。它是模运算符。助记符表示零,但确实如此!

更糟糕的是布尔运算符。 &和”是有意义的,只是有两个不同的&运算符执行两项不同的操作,并且在语法上有效的情况下(无论哪种情况),只要其中一个有效,则两者在语法上都是有效的,尽管在任何给定的情况下实际上只有一个有意义。那只是造成混乱的秘诀!的异或,并没有运营商更糟糕,因为它们不使用mnemonically有用的符号,而不是一致的无论是。(没有double- xor运算符,逻辑和按位运算符使用两个不同的符号,而不是一个符号和加倍的版本。)

而且,更糟的是,&*运算符被重用用于完全不同的事情,这使得该语言对于人员和编译器都难以解析。(这A * B是什么意思?它完全取决于上下文:是A类型还是变量?)

坦率地说,我从未与Dennis Ritchie交谈过,也没有问过他使用该语言做出的设计决策,但实际上,感觉像是操作员背后的哲学是“出于符号目的的符号”。

与Pascal相比,Pascal具有与C相同的运算符,但是在表示它们时却有不同的理念:运算符应该直观易读。 算术运算符相同。模运算符是单词mod,因为键盘上没有明显表示“模数”的符号。逻辑运算符是andorxornot,只有有各自的一个; 编译器根据是对布尔值还是对数字进行运算,就知道是需要布尔型还是按位版本,从而消除了整个错误类别。这使得帕斯卡尔码更容易比C代码来理解,特别是在现代的编辑与语法高亮所以运算符和关键字从标识符视觉上不同。


4
Pascal根本不代表不同的哲学。如果你读Wirth的论文,他用喜欢的东西符号andor所有的时间。但是,当他开发Pascal时,是在使用6位字符的Control Data大型机上进行的。这迫使决定使用的话很多事情,原因很简单,该字符集根本不具有几乎为符号太多的空间和比如像ASCII。我同时使用了C和Pascal,并且发现C更具可读性。您可能更喜欢Pascal,但这只是口味问题,而不是事实。
杰里·科芬

为了补充有关Wirth的@JerryCoffin注释,其后续语言(Modula,Oberon)使用了更多符号。
AProgrammer 2012年

@AProgrammer:他们从来没有去过任何地方。;)
Mason Wheeler 2012年

我认为|(并通过扩展||)是有意义的。您还经常看到它在其他上下文(例如语法)中进行交替显示,看起来它正在拆分两个选项。
Tikhon Jelvis'3

1
基于字母的关键字的一个优点是它们的空间比符号大得多。例如,帕斯卡(Pascal)风格的语言可以包含用于“模数”和“余数”以及整数与浮点除法(除其操作数类型之外其他含义)的运算符。
supercat 2014年

1

我认为,当运算符的功能紧密反映其熟悉的数学目的时,它们的可读性会更高。例如,执行加法和减法时,诸如+,-等标准运算符比加法或减法更具可读性。必须明确定义操作员的行为。例如,对于列表串联,我可以容忍+含义的重载。理想情况下,该操作不会产生副作用,返回一个值而不是进行变异。

不过,我一直在使用快捷运算符来处理诸如fold之类的功能。显然,与他们合作会有更多经验,但我发现foldRight比更具可读性/:


4
也许使用频率也随之而来。我不会以为您fold会经常让操作员节省大量时间。这也可能就是为什么LISPy(+1 2 3)看起来很奇怪但(add 1 2 3)不太奇怪的原因。我们倾向于将运算符视为严格的二进制。Parsec的>>.样式运算符可能很愚蠢,但是至少您在Parsec中所做的主要工作是组合解析器,因此它的运算符确实得到了很多使用。
CodexArcanum 2012年

1

运算符的问题在于,与可以替代使用的感官(!)方法名称的数量相比,运算符的数量很少。副作用是用户可定义的运算符往往会引入很多重载。

当然,该行C = A * x + b * c比容易写和读C = A.multiplyVector(x).addVector(b.multiplyScalar(c))

或者,如果您头脑中所有这些运算符的所有重载版本都可以轻松编写。然后,您可以在修复倒数第二个bug的同时阅读它。

现在,对于将要用完的大多数代码来说,这很好。“该项目通过了所有测试,并开始运行” –对于许多我们可能想要的软件而言。

但是,当您开发进入关键领域的软件时,事情会有所不同。安全关键的东西。安全。使飞机处于空中或使核设施保持运转的软件。或加密高度机密电子邮件的软件。

对于专门从事审计代码的安全专家来说,这可能是一场噩梦。大量用户定义的运算符和运算符重载的混合使用会使他们处于不愉快的境地,因为他们很难弄清楚最终将要运行什么代码。

因此,正如原始问题中所述,这是一个非常主观的主题。尽管有很多关于如何使用运算符的建议听起来很明智,但从长远来看,只有少数几个的组合可能会带来很多麻烦。


0

我想最极端的例子是APL,以下程序是“生活游戏”:

life←{↑1 ⍵∨.∧3 4=+/,¯1 0 1∘.⊖¯1 0 1∘.⌽⊂⍵}

而且,如果您不用花几天时间就能了解参考手册的全部含义,那么祝您好运!

问题是您拥有几乎每个人都能直观理解的一组符号: +,-,*,/,%,=,==,&

其余部分需要一些解释才能理解,并且通常特定于特定语言。例如,没有明显的符号来指定您想要数组的哪个成员,[],()甚至“”。已经使用过,但同样没有明显的关键字可以轻松使用,因此有必要明智地使用运算符。但不是太多。

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.