JavaScript 中分组运算符“()”的功能与 Haskell 或其他编程语言有何不同?

smi*_*nof 10 haskell functional-programming algebra

JavaScript 中的分组运算符 ( )

分组运算符( )控制表达式中求值的优先级。


( )JavaScript 本身的功能与 Haskell 或任何其他编程语言有何不同?

换句话说,

( )编程语言本身的功能是否受到评估策略的影响?

也许我们可以分享下面的代码:

a() * (b() + c())
Run Code Online (Sandbox Code Playgroud)

讨论这里的主题,但不限于示例。

请随意使用您自己的例子来说明。谢谢。


Ben*_*Ben 10

分组括号在 Haskell 中的含义与高中数学中的含义相同。他们将子表达式分组为单个术语。这也是它们在 Javascript 和大多数其他编程语言1中的含义,因此如果您以正确的方式学习了 Haskell,则无需为来自其他常见语言的 Haskell 重新学习它。

不幸的是,这种分组通常被解释为“括号内的表达式必须先于括号外的表达式求值”。这来自于用严格的语言(如高中数学)评估表达式时所遵循的步骤顺序。然而,分组实际上并不是关于您评估事物的顺序,即使在这种情况下也是如此。相反,它用于确定表达式实际上是什么,在对表达式执行任何操作之前您需要知道这一点,更不用说评估它了分组通常作为解析语言的一部分来解决,与任何运行时评估发生的顺序完全分开。

让我们考虑一下OP的例子,但我要声明函数调用语法f{}不仅仅是f()为了避免将相同的符号用于两个目的。所以在我新编写的语法中,OP的示例是:

a{} * (b{} + c{})
Run Code Online (Sandbox Code Playgroud)

这意味着:

  • a以零参数调用
  • b以零参数调用
  • c以零参数调用
  • +被两个参数调用;左边的参数是 的结果b{},右边的参数是 的结果c{}
  • *对两个参数进行调用:左边的参数是 的结果a{},右边的参数是 的结果b{} + c{}

请注意,我没有对这些进行编号。这只是存在的子表达式的无序列表,而不是我们计算它们的顺序。

如果我们的示例没有使用分组括号,那么它就是a{} * b{} + c{},而我们的子表达式列表将是:

  • a以零参数调用
  • b以零参数调用
  • c以零参数调用
  • +被两个参数调用;左边的参数是 的结果a{} * b{},右边的参数是 的结果c{}
  • *对两个参数进行调用:左边的参数是 的结果a{},右边的参数是 的结果b{}

这只是与第一个不同的子表达式集(因为整个表达式并不意味着相同的事情)。这就是分组括号的全部作用;它们允许您指定哪些子表达式作为参数传递给其他子表达式2

现在,用严格的语言来说,“什么被传递给什么”对于评估顺序非常重要在严格的语言中,不可能在“a{} + b{}没有首先评估的结果”上调用任何内容a{} + b{}(并且我们不能+在没有评估a{}和的情况下调用b{})。但是,即使分组决定了传递给什么,并且这部分决定了评估顺序3、分组并不是真正“关于”求值顺序。求值顺序可能会因为更改表达式中的分组而发生变化,但是更改分组会使其成为不同的表达式,因此几乎任何内容都可能因更改分组而发生变化!

像 Haskell 这样的非严格语言特别清楚地表明,分组与求值顺序无关,因为在非严格语言中,您可以在实际求值结果之前将“结果a{} + b{}”作为参数传递。因此,在上面的子表达式列表中,任何顺序都可能是可能的。分组根本不能决定它。

除了子表达式的分组之外,语言还需要其他规则来确定求值顺序(如果它想指定顺序),无论是严格的还是惰性的。因此,由于无论如何您都需要其他规则来确定它,因此最好(在我看来)将评估顺序视为与分组完全独立的概念。当你学习高中数学时,将它们混合起来似乎是一条捷径,但在更一般的环境中这只是一个障碍。


1在语法大致类似于 C 的语言中,括号用于调用函数,如func(arg1, arg2, arg3). OP本身在他们的a() * (b() + c())示例中采用了这种语法,这可能是调用a, b, 和c作为函数(向它们每个传递零参数)。

这种用法与括号分组完全无关,并且 Haskell 不使用括号来调用函数。但可能会出现一些混乱,因为在类似 C 的语法中使用括号来调用函数的必要性有时会避免对括号进行分组的需要,例如传递给func(2 + 3) * 6它的值是明确的,并且结果将乘以 6;在 Haskell 语法中,您需要一些分组括号,因为没有括号会被解释为与 相同,而 则不是。2 + 3funcfunc 2 + 3 * 6(func 2) + (3 * 6)func (2 + 3) * 6

类似 C 的语法并不只是将括号用于两个完全不相关的目的;Haskell 也重载了括号,除了分组之外,还用于不同的用途。Haskell 还使用它们作为编写元组(例如 )的语法的一部分,以及您可能想或不想将其视为“空元组”的(1, True, 'c')单元类型/值。()


2这也是运算符的结合性和优先级规则的作用。如果不知道 的*优先级高于+a * b + c则存在歧义;没有办法知道这意味着什么。根据优先规则,我们知道这a * b + c意味着“c与相乘的结果相加a” ,但是当我们想要“与相加的结果相乘”b时,我们现在无法写下我们的意思,除非我们也允许分组括号。abc


3即使在严格的语言中,分组也只能部分确定评估顺序。如果你看一下我上面的“子表达式列表”,很明显,在严格的语言中,我们需要尽早评估a{}, b{}, 和,但是没有任何东西可以决定我们是否首先评估然后然后然后,或者首先评估然后然后然后,或任何其他排列。我们甚至可以只评估最里面的/应用程序中的两个(以任一顺序),然​​后在评估第三个命名函数调用之前评估运算符应用程序,等等。c{}a{}b{}c{}c{}a{}b{}+*

即使在严格的语言中,在调用参数之前评估参数的需要也不能完全确定分组的评估顺序。分组只是提供一些限制。


4一般来说,在惰性语言中,根据需要,对给定调用的评估一次发生一点,因此实际上,通常给定表达式中的所有子评估都可以以复杂的方式交错(并非精确地发生)一个接一个)无论如何。

  • @IvenMarquardt 指定从左到右的求值顺序在不纯的语言中很有意义,因此程序员可以预测副作用的顺序,但它仍然不需要**那样。例如,我相信 C 和 C++ 不会指定同一调用的参数的求值顺序(只是它们都会在调用之前求值);编译器可以自由地重新排序以进行优化,如果顺序很重要,程序员有责任实现这一点并将表达式拉出到单独的赋值语句中,以便可以控制顺序。 (3认同)

smi*_*nof 2

我自己(发问者)回答,但是,我愿意接受检查,并且仍在等待您的回答(不是基于意见):

每种语言中的分组运算符共享()组成依赖图的通用功能。

在数学、计算机科学和数字电子学中,依赖图是表示多个对象相互依赖的有向图。可以从依赖图中导出尊重给定依赖关系的评估顺序或不存在评估顺序。

依赖图 1 依赖图 2

分组运算符本身的功能()不受任何语言的评估策略的影响。