语法设计-为什么在不传递任何参数的情况下使用括号?


66

在许多语言中,语法function_name(arg1, arg2, ...)用于调用函数。当我们想不带任何参数地调用函数时,我们必须这样做function_name()

我感到奇怪的是,编译器或脚本解释器需要()成功将其检测为函数调用。如果已知一个变量是可调用的,那为什么还function_name;不够?

另一方面,在某些语言中,我们可以执行以下操作:function_name 'test';甚至function_name 'first' 'second';可以调用函数或命令。

我认为如果只需要用括号声明优先顺序,括号会更好,而在其他地方则是可选的。例如,doing if expression == true function_name;应该和一样有效if (expression == true) function_name();

我认为最烦人的事情是'SOME_STRING'.toLowerCase()原型函数显然不需要参数时。设计师为什么决定反对更简单的选择'SOME_STRING'.lower

免责声明:不要误会我的意思,我喜欢类似C的语法!;)我只是在问是否会更好。需求是否()具有任何性能优势,还是使理解代码更容易?我真的很好奇原因到底是什么。


103
如果想将一个函数作为参数传递给另一个函数,该怎么办?
Vincent Savard

30
只要需要人类阅读代码,可读性就是王者。
图兰斯·科尔多瓦

75
您问的是具有可读性的内容(),而帖子中突出的内容是if (expression == true)声明。您担心多余()的,然后使用多余的== true:)
David Arno

15
Visual Basic允许这样做
edc65 '16

14
在Pascal中这是强制性的,您只对使用参数的函数和过程使用parens。
RemcoGerlich

Answers:


251

对于使用语言一流的功能,它很常见的语法的功能是:

a = object.functionName

调用该函数的行为是:

b = object.functionName()

a在上面的示例中,将引用上面的函数(您可以通过进行调用a()),同时b包含函数的返回值。

尽管某些语言可以在不带括号的情况下进行函数调用,但是它们是在调用函数还是简单地引用函数时可能会造成混淆。


9
您可能要提到的是,这种差异仅在存在副作用的情况下才有意义-对于纯功能而言,并不重要
Bergi

73
@Bergi:这对于纯函数很重要!如果我有一个make_list将其参数打包到列表中的函数,则将函数f(make_list)传递给f或传递空列表之间会有很大的区别。
user2357112 '16

12
@Bergi:即使那样,我仍然希望能够将一个SATInstance.solve或其他一些昂贵的纯函数传递给另一个函数,而无需立即尝试运行它。如果someFunction根据是否someFunction声明为纯净而进行其他操作,这也会使事情变得很尴尬。举例来说,如果我有(伪)func f() {return g}func g() {doSomethingImpure}func apply(x) {x()},然后apply(f)调用f。如果然后我声明它f是纯净的,则突然apply(f)传递gapply,并apply调用g并做一些不纯净的事情。
user2357112 '16

6
@ user2357112评估策略与纯度无关。在评估什么可能(但可能很尴尬)时,使用调用的语法作为注解,但是它不会改变结果或其正确性。对于您的示例apply(f),如果g不纯(apply也是?),那么整个事情都是不纯的,并且当然会崩溃。
Bergi '16

14
例如Python和ECMAScript,它们实际上并没有“方法”的核心概念,相反,您有一个返回匿名第一类函数对象的命名属性,然后调用了该匿名第一类函数。具体地说,在Python和ECMAScript中,foo.bar()不是一个操作“ bar对象的调用方法foo”,而是两个操作:“引用bar对象的字段,foo 然后调用从该字段返回的对象”。所以,.是的字段选择运营商,并()电话运营商,他们是独立的。
约尔格W¯¯米塔格

63

确实,Scala允许这样做,尽管遵循一个约定:如果该方法有副作用,则无论如何都应使用括号。

作为一个编译器作者,我发现保证括号的存在非常方便;我永远都知道这是一个方法调用,对于特殊情况,我不必分叉。

作为程序员和代码阅读器,即使没有传递任何参数,括号的出现也毫无疑问地是一个方法调用。

参数的传递不是方法调用的唯一定义特征。为什么我将无参数方法与具有参数的方法区别对待?


谢谢,您给出了为什么这很重要的正当理由。您还可以举例说明为什么语言设计人员应在原型中使用函数吗?
David Refoua '16

括号的存在毫无疑问是我完全同意的方法调用。我更喜欢编程语言,而不是隐式的。
科里·奥格本

20
Scala实际上比这要微妙一些:虽然其他语言只允许一个参数列表包含零个或多个参数,但是Scala允许零个或多个参数列表具有零个或多个参数。因此,def foo()是一个带有一个空参数列表def foo的方法,是一个没有参数列表的方法,两者是不同的!通常,不带参数列表的方法需要不带参数列表的调用,而带空参数列表的方法需要带空参数列表的调用。调用不带参数列表的方法与空的说法实际上是...
约尔格W¯¯米塔格

19
…解释为使用空参数列表调用由方法调用返回apply的对象的方法,而根据上下文,调用具有空参数列表而没有参数列表的方法可能会被不同地解释为使用空参数列表调用方法,将η扩展为部分应用的方法(即“方法参考”),否则可能根本无法编译。而且,Scala遵循统一访问原则,在语法上不区分在没有参数列表的情况下调用方法与引用字段之间。
约尔格W¯¯米塔格

3
即使我因缺少必需的“仪式”(括号,括号,显式类型)而对Ruby表示赞赏时,我仍可以告诉我们方法括号的读取速度更快;但是曾经熟悉的惯用Ruby具有很好的可读性,并且编写起来肯定更快。
雷达波

19

这实际上是语法选择的一个非常微妙的偶然。我将介绍基于类型化lambda演算的功能语言。

在所说的语言中,每个函数都只有一个参数。我们通常认为的“多个参数”实际上是产品类型的单个参数。因此,例如,比较两个整数的函数:

leq : int * int -> bool
leq (a, b) = a <= b

接受一对整数。括号表示功能参数;它们用于对参数进行模式匹配。为了说服您这确实是一个参数,我们可以改用投影函数而不是模式匹配来解构该对:

leq some_pair = some_pair.1 <= some_pair.2

因此,括号确实是一种方便,它允许我们进行模式匹配并节省一些键入内容。它们不是必需的。

表面上没有参数的函数呢?这样的功能实际上具有领域Unit。的单个成员Unit通常写为(),因此这就是括号出现的原因。

say_hi : Unit -> string
say_hi a = "Hi buddy!"

要调用此函数,我们必须将其应用于type的值Unit,该值必须为(),因此最终编写say_hi ()

因此,实际上没有参数列表!


3
这就是为什么空括号()类似于勺子减去手柄的原因。
Mindwin '16

3
定义一个leq使用<而不是的函数<=会很混乱!
瑞安(KRyan)

5
如果除了“产品类型”之类的词组外还使用“元组”一词,则此答案可能更容易理解。
凯文(Kevin)

大多数形式主义会将int f(int x,int y)视为从I ^ 2到I的函数。Lambda演算是不同的,它不使用此方法来匹配其他形式主义中的高Arity。相反,lambda演算使用了currying。比较将是x->(y->(x <= y))。(x->(y->(x <= y))2 2将得出(y-> 2 <= y)和(x->(y->(x <= y))2 3将得出( y-> 2 <= y)3,其结果为2 <=
3。– Taemyr

1
@PeriataBreatta我不熟悉()用来代表该单位的历史。如果至少部分原因是类似于“无参数函数”,我不会感到惊讶。但是,语法还有另一种可能的解释。在类型的代数中,Unit实际上是产品类型的标识。二进制乘积写为(a,b),我们可以将一元乘积写为(a),因此逻辑扩展将是简单地写空乘积()
Gardenhead '16

14

例如,在Javascript中,使用不带()的方法名将返回函数本身,而不执行该函数。这样,您可以例如将函数作为参数传递给另一个方法。

在Java中,选择标识符后跟()或(...)表示方法调用,而没有()的标识符表示成员变量。这无疑可以提高可读性,因为您无疑是在处理方法还是成员变量。实际上,方法和成员变量都可以使用相同的名称,都可以使用它们各自的语法来访问它们。


4
+1只要需要人类阅读代码,可读性就是王道。
图兰斯·科尔多瓦

1
@TulainsCórdova我不确定perl是否同意您的意见。
拉切特

@Racheet:Perl的可能不会,但Perl Best Practices确实
slebetman

1
如果将@Racheet Perl编写为可读性很强。几乎每种非深奥语言都是相同的。简短而简洁的Perl对于Perl程序员来说非常容易阅读,就像Scala或Haskell代码对于那些使用过这些语言的人一样可读。我还可以以非常复杂的方式对您说德语,这在语法上是完全正确的,但是即使您精通德语,您仍然听不懂一个字。这也不是德国人的错。;)
simbabque

在Java中,它不会“识别”方法。它称之为。Object::toString标识一种方法。
njzk2

10

语法遵循语义,因此让我们从语义开始:

使用函数或方法的方式有哪些?

实际上,有多种方法:

  • 可以调用一个函数,带有或不带有参数
  • 一个函数可以被当作一个值
  • 可以部分应用一个函数(创建一个闭包,减少至少少一个参数,并关闭传递的参数)

对于不同的用途使用相似的语法是创建一种模棱两可的语言或至少创建一种令人困惑的语言的最佳方法(而且我们有足够的语言)。

在C和类似C的语言中:

  • 调用函数是通过使用括号将参数括起来(可能没有)来完成的,如下所示: func()
  • 将函数视为值可以通过简单地使用其名称来完成&func(如C创建一个函数指针)
  • 在某些语言中,您有用于部分应用的简写语法;Java允许someVariable::someMethod(例如,限于方法接收器,但仍然有用)

请注意每种用法如何具有不同的语法,使您可以轻松区分它们。


2
好吧,“轻松”是其中一种“只有在已经理解的情况下才清楚”的情况之一。初学者完全有理由指出,如果不解释为什么某些标点符号具有它的含义,那绝不是显而易见的。
埃里克·利珀特

2
@EricLippert:确实,轻松不一定意味着直观。尽管在这种情况下,我会指出括号在数学中也用于函数“调用”。话虽这么说,我发现许多语言的初学者在概念/语义上都比在语法上挣扎得多。
Matthieu M.

这个答案混淆了“ currying”和“ partial application”之间的区别::Java中的运算符是部分应用程序运算符,而不是循环运算符。部分应用程序是一种操作,它接受一个函数和一个或多个值,并返回一个接受较少值的函数。curinging只对函数起作用,而不对值起作用。
Periata Breatta '16

@PeriataBreatta:好点,固定。
Matthieu M.

8

在具有副作用的语言中,IMO确实有助于区分变量(在理论上无副作用)读取变量

variable

并调用一个函数,这可能会产生副作用

launch_nukes()

OTOH,如果没有副作用(除了在类型系统中编码的副作用),那么甚至没有区别区分读取变量和调用不带参数的函数的意义。


1
请注意,OP提出了一种情况,其中对象是唯一的参数,并且出现在函数名称之前(这也提供了函数名称的作用域)。noun.verb语法在含义上可能很清晰,noun.verb()而且不太混乱。同样,member_返回左引用的函数可能比典型的setter函数提供更友好的语法:obj.member_ = new_valuevs. obj.set_member(new_value)_后缀是提示,表明“暴露”让人想起“隐藏”函数的_ 前缀)。
保罗·克莱顿

1
@ PaulA.Clayton我看不出我的答案没有涵盖:如果noun.verb是函数调用,您如何将其与单纯的成员访问区分开?
Daniel Jour

1
关于读取变量在理论上没有副作用,我只想这样说:始终提防虚假的朋友
贾斯汀时间

8

没有其他答案试图解决这个问题:一种语言的设计应该有多少冗余?因为即使您可以设计一种语言来x = sqrt y将x设置为y的平方根,但这也不意味着您必须这样做。

在没有冗余的语言中,每个字符序列都意味着某种含义,这意味着如果您犯了一个错误,就不会收到错误消息,程序将执行错误的操作,这可能与您的操作有很大不同预期且很难调试。(正如使用过正则表达式的人都会知道的。)一点冗余是一件好事,因为它可以检测到您的许多错误,并且冗余越多,诊断就越有可能是准确的。当然,现在可以走得太远(这些天我们都不想用COBOL编写),但是有一个正确的平衡点。

冗余还有助于提高可读性,因为有更多线索。没有冗余的英语将很难阅读,编程语言也是如此。


从某种意义上说,我同意:编程语言应具有“安全网”机制。但是我不同意语法中的“一点冗余”是解决该问题的好方法–这是样板,而它主要要做的是引入噪声,使错误更难发现,并为语法错误提供了可能性,从而分散了真正的错误。有助于提高可读性的冗长程度与语法无关,而与命名约定有关。到目前为止,安全网最有效地实现为强类型系统,其中大多数随机更改都会使程序类型错误。
左右左转

@leftaroundabout:我喜欢根据汉明距离来考虑语言设计决策。如果两个构造的含义不同,则使它们至少在两种方式上有所不同,并且具有仅在一种方式上有所不同的形式,这通常会对生成编译器队列有所帮助。语言设计人员通常不负责确保标识符易于区分,但是如果例如赋值需要:=和比较==,并且=仅可用于编译时初始化,则拼写错误将比较转换为赋值的可能性将大大降低。
超级猫

@leftaroundabout:同样,如果我正在设计一种语言,则可能需要()调用零参数函数,但需要令牌以“获取”函数的地址。前一个动作更为常见,因此在不太常见的情况下保存令牌并不是那么有用。
超级猫

@supercat:好吧,我再次认为这是在错误的水平上解决了问题。分配和比较在概念上是不同的操作,功能评估和功能获取分别是。因此,它们应该具有明显不同的类型,如果运算符的汉明距离很短,则不再重要,因为任何拼写错误都会导致编译时类型错误。
左转大约

@leftaroundabout:如果要对布尔型变量进行赋值,或者函数的返回类型本身就是指向函数的指针(或(在某些语言中为实际函数),则用赋值代替比较,或使用功能引用进行功能调用,可能会产生一个程序,该程序在语法上有效,但含义与原始版本完全不同。类型检查通常会发现一些此类错误,但是使语法不同似乎是一种更好的方法。
超级猫

5

在大多数情况下,这些是语言语法的句法选择。对于语法而言,将各个单独的构造放在一起时(相对)是明确的是有用的。(如果像某些C ++声明那样存在歧义,则必须有特定的解析规则。)编译器没有猜测的余地;必须遵循语言规范。


Visual Basic以各种形式区分不返回值的过程和函数。

过程必须作为语句调用,并且不需要parens,只要有逗号分隔的参数(如有)即可。函数必须作为表达式的一部分来调用,并且需要括号。

这是相对不必要的区分,这使得两种形式之间的手动重构比必须的更加痛苦。

(另一方面,Visual Basic ()对数组引用使用与函数调用相同的括号,因此数组引用看起来像函数调用。这简化了将数组手动重构为函数调用的过程。因此,我们可以考虑使用[]的其他语言对于数组引用,但我离题了……)


在C和C ++语言中,变量的内容通过使用其名称自动访问,并且如果要引用变量本身而不是变量的内容,则可以应用一元运算&符。

这种机制也可以应用于函数名称。原始函数名称可能意味着函数调用,而一元运算&符将用于将函数(自身)称为数据。就我个人而言,我喜欢使用与变量相同的语法访问无副作用的无参数函数的想法。

这是完全合理的(与此同时,其他语法选择也是如此)。


2
当然,虽然Visual Basic与过程调用相比在过程调用上缺少括号是不必要的区别,但在它们之间添加必需的括号将是语句之间的不必要区别,其中许多是使用BASIC派生的语言产生的效果非常明显让人联想到程序。自从我在MS BASIC版本中进行任何工作以来已经有一段时间了,但是它仍然不支持语法call <procedure name> (<arguments>)吗?可以肯定的是,当我在另一生中进行QuickBASIC编程时,这是使用过程的一种有效方式……
Periata Breatta 2016年

1
@PeriataBreatta:VB仍然允许“调用”语法,并且在要调用的东西是表达式的情况下有时会要求使用它(例如,可以说“调用If(someCondition,Delegate1,Delegate2)(Arguments)”,但直接调用“ If”运算符的结果作为独立语句无效]。
超级猫

@PeriataBreatta我喜欢VB6的无括号过程调用,因为它使类似DSL的代码看起来不错。
Mark Hurd

@supercat在LinqPad中,令人惊讶的是,我经常需要使用Call一种在“正常”生产代码中几乎不需要的方法。
Mark Hurd

5

一致性和可读性。

如果我得知您X像这样调用函数:X(arg1, arg2, ...),那么我希望它在没有参数的情况下以相同的方式工作:X()

现在,我同时了解到您可以将变量定义为一些符号,并像这样使用它:

a = 5
b = a
...

现在,当我找到这个时我会怎么想?

c = X

你的猜测和我的一样。在正常情况下,X是变量。但是,如果我们走您的路,那也可能是一个功能!我是否还记得哪个符号映射到哪个组(变量/功能)?

我们可以施加人为的约束,例如“函数以大写字母开头,变量以小写字母开头”,但这不是必需的,而且会使事情变得更复杂,尽管有时可能有助于某些设计目标。

旁注1:其他答案完全忽略了另一件事:语言可能允许您对函数和变量使用相同的名称,并按上下文进行区分。参见Common Lisp示例。函数x和变量x完美共存。

旁注2:可接受的答案向我们显示了语法:object.functionname。首先,它不适用于具有一流功能的语言。其次,作为程序员,我会将其视为附加信息:functionname属于object。不管object是对象,类还是名称空间都没关系,但是它告诉我它属于某处。这意味着您必须object.为每个全局函数添加人工语法,或者创建一些语法object来容纳所有全局函数。

无论哪种方式,您都会失去为函数和变量使用单独的命名空间的能力。


对。一致性是一个足够充分的理由,是不停的(不是您的其他观点是无效的,而是事实)。
马修(Matthew)

4

要添加其他答案,请使用以下C示例:

void *func_factory(void)
{
        return 0;
}

void *(*ff)(void);

void example()
{
        ff = func_factory;
        ff = func_factory();
}

如果调用运算符是可选的,则将无法在函数分配和函数调用之间进行区分。

在缺少类型系统的语言(例如JavaScript)中,这甚至会带来更多问题,其中JavaScript无法使用类型推断来确定什么是函数,什么不是函数。


3
JavaScript不缺少类型系统。它缺少类型声明。但它完全意识到不同类型的价值之间的区别。
Periata Breatta

1
Periata Breatta:关键是Java变量和属性可以包含任何值的类型,因此在代码阅读时,您不能总是知道它是否是一个函数。
reinierpost

例如,此功能做什么?function cross(a, b) { return a + b; }
Mark K Cowan

@MarkKCowan它遵循以下规则:ecma-international.org/ecma-262/6.0/…–
curiousdannii
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.