C 系语言有一套自恰的 expression 风格的类型声明方式:
int *p;
意思是对 p 进行 dereference 后得到的表达式类型为int
,即不声明这个变量本身的类型,而是通过一个表达式来确定“这个变量是怎么使用的”进而确定其类型。
自然地,这样声明函数:
int main(int argc,char *argv[]);
表示对main(int argc,char *argv[])
进行调用后得到的表达式类型为int
。
即你需要解一个 f(x)=y 的方程,x 为你声明的变量的类型,y 为你声明的表达式类型。
但是当表达式比较复杂(尤其是变量名还可以省略)的时候,https://blog.golang.org/declaration-syntax 有这么一个例子:
int (*(fp)(int ()(int, int), int))(int, int);
这种“名称在中间,类型放两边”的中值声明方式可谓是非常丑陋,对应的 Go 的声明如下:
f func(func(int,int) int, int) func(int, int) int
可以一眼就看出来,f 是一个返回另一个函数的函数,f 的参数一是接受两个int
参数,返回一个int
的函数;参数二是一个int
;返回的函数是一个接受两个int
参数,返回一个int
的函数。
但这个可读性的提升不一定是因为后置,可能有些习惯了 C/C++,Java 的人更喜欢这样:
int func(int,int) func(int func(int,int),int) f;
但我个人觉得这样更容易引起混淆,而且还有一种头重脚轻的感觉。它跟你熟悉的东西似是而非,却又截然不同。
因为这已经不是开头说的那一种 expression 解方程风格的声明方式了,而是在这基础上的一种延伸,类似于 pattern match,更形象的说是把类型当作占位符,匹配两边的shape。这也是各种现代的语言( Go,Rust,TypeScript,Kotlin,Swift 等)采用的方式。
在这种模式下一个变量的类型就是它的 shape,foo()
的类型就是Fn()
,而不再写成void *(void)
;
(&3,true)
的类型就是(&i32,bool)
;
struct{a:1,b:2}
的类型就是struct{a:i32,b:i32}
;
返回值更不必多说,去掉 C 风格后自然放在后面更符合直觉。
后置的另一大好处就是在使用现代编程语言的 type inference 时保持一致性,
let x = 1;
let y:i32 = 1;
显然要比:
int x = 1;
var y = 1;
具有更好的一致性,编译器无需判断开头的是类型还是关键字。
总而言之,C 风格的声明方式是自恰的,也可以说是优雅的;但是却太过炫技,不够 ergonomic 。
而这套类型后置,与 C 完全迥异的风格,咋一看觉得 exotic,熟悉了之后越用越喜欢。再回头写传统的 C 系语言时就总想把这些玩意扫进历史的垃圾堆(:
1
Jirajine OP 关于 C 的声明风格详细可以参考这里: http://c-faq.com/decl/spiral.anderson.html 它需要你的眼睛“螺旋式”parse 代码
|
2
turi 2020-06-03 16:40:44 +08:00
我喜欢 c++的
using funcA=int (int,int); 这种别名 |
3
Jirajine OP @turi #2 当然,正式因为类型表示太坑,用别名之后会舒服很多。
但 C++这个别名我也要再黑一下,它有两种定义方式: ```c++ typedef char *strs[10]; using strs = char*[10]; ``` 两个关键字干同一件事语法还不一样本身就很不协调了,这两个方式还都有问题,第一种把定义变量的格式规定成类型的名字,容易混淆;第二种类型名抽出来看似清晰了,但 expression 中的变量名又省略了,类型复杂的时候还是很难看清楚。 还有个 trick 通过原型定义: ``` char *proto[10]; using strs = decltype(proto); ``` 但同样不怎么好看。 |
4
wutiantong 2020-06-03 17:39:57 +08:00
@Jirajine 第一种是现在不推荐用的,第二种我个人觉得没啥问题。
其实你所吐槽的点在我看来还是集中在函数类型上,这与类型的后置前置似乎无关吧? 肉眼 parse 那种复合类型的确费劲,但是通常代码里也就几处像这样的,不应该通篇都是——尤其是现在还有 auto 加持的情况下——所以代码的可读性一般也不会受此影响吧。 |
5
ZingLix 2020-06-03 18:09:27 +08:00 via Android
@Jirajine typedef 应该可以算是 c 语言留下的毒瘤了,c++一大半的糟糕设计都是为了兼容 c 产生的
|
6
Nich0la5 2020-06-03 18:14:54 +08:00 via Android
要传教 rust 就直说呗
|
7
Jirajine OP @wutiantong #4 不只是函数类型,是整个 C 的类型系统的这种 expression 解方程表示法。除了函数调用,derefernce,index, 还有 top-level/low-level const 等修饰符结合到一块同样也混乱。存在函数类型的时候还有个问题就是 expression 中括号既表示调用函数,也用来强调运算符优先级,正文中 golang 博客上那个例子就是结果。
至于后置,主要是新的类型系统完全不同于以往的 expression,后置一来适合 type inference,二来不容易和以往的混淆,尤其是你习惯螺旋 parse 类型以后。 像: ``` let buffer:[u8;512]; let myfunc:Fn(i32,i32)->i32; ``` 前置的话和以前的习惯似是而非反而更难受: ```some [u8;512] buffer; //c-style u8 buffer[512]; Fn(i32,i32)->i32 myfunc; //c-style i32 *myfunc(i32,i32); ``` |
8
Jirajine OP |
9
richard1122 2020-06-03 18:54:51 +08:00 via Android
进一步的话我觉得 f# 挺不错
|
10
mirrorman 2020-06-03 19:03:04 +08:00
C++比较难得的是缝合了这么多年向后兼容以及语法大致上的一体性还凑活,要是早扔掉兼容 C 去设计肯定不是这个鸟样子,动不动就加关键字我真是吐了,但反早出点 Attribute 之类的设计(不是编译器厂商私活的 attr )也比现在这么多关键字和复杂的规则强
|
11
secondwtq 2020-06-03 19:15:14 +08:00 via iPhone
不好写 parser ……
|
12
Fule 2020-06-03 21:55:04 +08:00 4
你是说这样的?
`Dim str As String` 😁 |
13
XIVN1987 2020-06-04 09:00:58 +08:00
我觉得还是 C++的方式比较好:
int i = 1; auto i = 1; 不管是写 int 还是写 auto 都在一个位置,,一致性更好 |
14
liaojl 2020-06-04 10:10:22 +08:00
类型后置有个问题,就是 IDE 不能自动补全变量名。
``` person Person //变量 person 要自己全部打出来 ``` 如果是后置的话 ``` Person person; //person 打出第一个字母 p 的时候 IDE 就已经可以补全 person 了 ``` |
15
aguesuka 2020-06-04 12:21:20 +08:00 via Android
因为我喜欢 golang
又因为 golang 是类型后置的语言 所以我喜欢的语言是类型后置的语言 我喜换类型后置的语言 |