我刚刚在 Go 常见问题解答中遇到了这个问题,它让我想起了困扰我一段时间的事情。不幸的是,我真的不明白答案是什么。
似乎几乎所有非 C 类语言都将类型放在变量名之后,如下所示:
var : int
纯粹出于好奇,这是为什么呢?选择其中一个有优势吗?
我刚刚在 Go 常见问题解答中遇到了这个问题,它让我想起了困扰我一段时间的事情。不幸的是,我真的不明白答案是什么。
似乎几乎所有非 C 类语言都将类型放在变量名之后,如下所示:
var : int
纯粹出于好奇,这是为什么呢?选择其中一个有优势吗?
正如 Keith Randall 所说,存在解析问题,但这不是他所描述的。“不知道它是声明还是表达式”根本无关紧要 - 你不关心它是表达式还是声明,直到你已经解析了整个事情,此时歧义得到了解决。
使用上下文无关的解析器,类型是在变量名之前还是之后都没有关系。重要的是,您无需查找用户定义的类型名称即可理解类型规范——您无需了解之前的所有内容即可理解当前标记。
Pascal 语法是上下文无关的 - 如果不完全,至少 WRT 这个问题。变量名在前这一事实不如冒号分隔符和类型描述的语法等细节重要。
C 语法是上下文相关的。为了让解析器确定类型描述在哪里结束以及哪个标记是变量名,它需要已经解释了之前出现的所有内容,以便它可以确定给定的标识符标记是变量名还是只是另一个标记类型描述。
因为 C 语法是上下文相关的,所以使用传统的解析器生成器工具(例如 yacc/bison)解析非常困难(如果不是不可能的话),而使用相同的工具很容易解析 Pascal 语法。也就是说,现在有可以处理 C 甚至 C++ 语法的解析器生成器。虽然它没有正确记录或在 1.? release 等,我个人最喜欢的是Kelbt,它使用回溯 LR 并支持语义“撤消” - 当推测分析结果错误时,基本上撤消对符号表的添加。
在实践中,C 和 C++ 解析器通常是手写的,混合了递归下降和优先解析。我假设这同样适用于 Java 和 C#。
顺便说一句,C++ 解析中与上下文相关的类似问题已经造成了很多麻烦。C++0x的“替代函数语法”通过将类型规范移动到末尾并将其放在分隔符之后来解决类似的问题 - 非常类似于函数返回类型的 Pascal 冒号。它并没有摆脱上下文敏感性,但采用类似 Pascal 的约定确实使它更易于管理。
您所说的“大多数其他”语言是更具声明性的语言。他们的目标是让你按照你的想法进行更多的编程(假设你没有被限制在命令式思维中)。
type last 读作“创建一个名为 NAME 类型的变量”
这当然与“创建一个名为 NAME 的类型”相反,但是当您考虑它时,值的用途比类型更重要,类型只是对数据的编程约束
如果变量的名称从第 0 列开始,则更容易找到变量的名称。
比较
QHash<QString, QPair<int, QString> > hash;
和
hash : QHash<QString, QPair<int, QString> >;
现在想象一下您的典型 C++ 标头的可读性有多大。
越来越多的趋势是根本不说明类型,或者选择性地说明类型。这可能是一种动态类型语言,其中变量上确实没有类型,或者它可能是一种从上下文推断类型的静态类型语言。
如果类型有时是给定的,有时是推断的,那么如果可选位随后出现,则更容易阅读。
还有一些趋势与一种语言是否认为自己来自 C 学校或功能学校或其他什么有关,但这些都是浪费时间。那些在其前辈上有所改进并值得学习的语言是那些愿意接受来自所有不同学校的基于优点的输入,而不是对功能的遗产挑剔的语言。
在形式语言理论和类型理论中,它几乎总是写成var: type
. 例如,在类型化 lambda 演算中,您会看到包含以下语句的证明:
x : A y : B
-------------
\x.y : A->B
我认为这并不重要,但我认为有两个理由:一个是“x : A”被读作“x 属于 A 类型”,另一个是类型就像一个集合(例如int
集合整数),符号与“x ε A”有关。
其中一些东西早于您正在考虑的现代语言。
“那些不记得过去的人注定要重蹈覆辙。”
在 Fortran 和 Algol 中,将类型放在变量之前已经足够无害了,但是在 C 中它变得非常丑陋,其中一些类型修饰符被应用在变量之前,其他的在之后。这就是为什么在C语言中你有这样的美女
int (*p)[10];
或者
void (*signal(int x, void (*f)(int)))(int)
以及用于解密此类乱码的实用程序(cdecl)。
在 Pascal 中,类型在变量之后,所以第一个例子变成
p: pointer to array[10] of int
对比
q: array[10] of pointer to int
在 C 中,它是
int *q[10]
在 C 中,您需要用括号将其与 int (*p)[10] 区分开来。在 Pascal 中不需要括号,只有顺序很重要。
信号函数为
signal: function(x: int, f: function(int) to void) to (function(int) to void)
还是一口,但至少在人类的理解范围之内。
公平地说,问题不在于 C 将类型放在名称之前,而在于它反常地坚持在名称之前放置一些零碎的部分,而在名称之后放置其他部分。
但是如果你试图把所有的东西都放在名字之前,这个顺序仍然不直观:
int [10] a // an int, ahem, ten of them, called a
int [10]* a // an int, no wait, ten, actually a pointer thereto, called a
因此,答案是:设计合理的编程语言将变量放在类型之前,因为结果对人类来说更具可读性。
这就是语言的设计方式。Visual Basic 一直都是这样。
大多数(如果不是全部)花括号语言将类型放在首位。这对我来说更直观,因为相同的位置也指定了方法的返回类型。所以输入放在括号中,输出放在方法名称的后面。
我不确定,但我认为这与“名称与名词”的概念有关。
本质上,如果您将类型放在首位(例如“int varname”),那么您就是在声明一个“名为 'varname' 的整数”;也就是说,您正在为类型的实例命名。但是,如果您先输入名称,然后输入类型(例如“varname : int”),那么您就是在说“这是 'varname';它是一个整数”。在第一种情况下,您正在为某事物的实例命名;在第二个中,您定义了一个名词并说明它是某事的一个实例。
这有点像您将桌子定义为一件家具。说“这是家具,我称它为'桌子'”(首先输入)与说“桌子是一种家具”(最后输入)不同。
我一直认为 C 的方式有点奇怪:用户必须隐式声明它们,而不是构造类型。不仅仅是在变量名之前/之后;通常,您可能需要在类型属性中嵌入变量名称(或者,在某些用法中,如果您实际声明一个名称,则在名称所在的位置嵌入一个空格)。
作为模式匹配的一种弱形式,它在某种程度上是可以理解的,但它似乎也没有提供任何特别的优势。而且,尝试编写(或读取)函数指针类型很容易使您超出可理解的范围。所以总的来说,C 的这个方面是一个劣势,我很高兴看到 Go 已经把它抛在了后面。
将类型放在首位有助于解析。例如,在 C 语言中,如果您声明了类似的变量
x int;
当您仅解析 时x
,您不知道 x 是声明还是表达式。与...对比
int x;
当你解析 时int
,你知道你在一个声明中(类型总是开始某种声明)。
鉴于解析语言的进步,这种轻微的帮助现在并不是非常有用。
Fortran 将类型放在首位:
REAL*4 I,J,K
INTEGER*4 A,B,C
是的,对于那些熟悉 Fortran 的人来说,那里有一个(非常微弱的)笑话。
有争论的余地,这比 C 更容易,当类型足够复杂时(例如,指向函数的指针),它将类型信息放在名称周围。
动态(欢呼@wcoenen)类型语言呢?您只需使用变量。