V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Jirajine
V2EX  ›  程序员

谈谈我为什么喜欢声明变量时类型后置的语言

  •  
  •   Jirajine · 2020-06-03 16:12:50 +08:00 · 3767 次点击
    这是一个创建于 1394 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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 系语言时就总想把这些玩意扫进历史的垃圾堆(:

    15 条回复    2020-06-04 12:21:20 +08:00
    Jirajine
        1
    Jirajine  
    OP
       2020-06-03 16:22:55 +08:00
    关于 C 的声明风格详细可以参考这里: http://c-faq.com/decl/spiral.anderson.html 它需要你的眼睛“螺旋式”parse 代码
    turi
        2
    turi  
       2020-06-03 16:40:44 +08:00
    我喜欢 c++的
    using funcA=int (int,int);
    这种别名
    Jirajine
        3
    Jirajine  
    OP
       2020-06-03 17:11:03 +08:00
    @turi #2 当然,正式因为类型表示太坑,用别名之后会舒服很多。
    但 C++这个别名我也要再黑一下,它有两种定义方式:
    ```c++
    typedef char *strs[10];
    using strs = char*[10];
    ```
    两个关键字干同一件事语法还不一样本身就很不协调了,这两个方式还都有问题,第一种把定义变量的格式规定成类型的名字,容易混淆;第二种类型名抽出来看似清晰了,但 expression 中的变量名又省略了,类型复杂的时候还是很难看清楚。
    还有个 trick 通过原型定义:
    ```
    char *proto[10];
    using strs = decltype(proto);
    ```
    但同样不怎么好看。
    wutiantong
        4
    wutiantong  
       2020-06-03 17:39:57 +08:00
    @Jirajine 第一种是现在不推荐用的,第二种我个人觉得没啥问题。
    其实你所吐槽的点在我看来还是集中在函数类型上,这与类型的后置前置似乎无关吧?
    肉眼 parse 那种复合类型的确费劲,但是通常代码里也就几处像这样的,不应该通篇都是——尤其是现在还有 auto 加持的情况下——所以代码的可读性一般也不会受此影响吧。
    ZingLix
        5
    ZingLix  
       2020-06-03 18:09:27 +08:00 via Android
    @Jirajine typedef 应该可以算是 c 语言留下的毒瘤了,c++一大半的糟糕设计都是为了兼容 c 产生的
    Nich0la5
        6
    Nich0la5  
       2020-06-03 18:14:54 +08:00 via Android
    要传教 rust 就直说呗
    Jirajine
        7
    Jirajine  
    OP
       2020-06-03 18:26:25 +08:00
    @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);
    ```
    Jirajine
        8
    Jirajine  
    OP
       2020-06-03 18:49:38 +08:00
    @Nich0la5 #6
    我也没写啥 rust 的特色,算不上传教吧。只是顺便黑了一把 cpp 而已。
    Go,Rust,TypeScript,Kotlin,Swift 我都很推荐,还有 MS 新开的 verona 项目我也很期待。

    @ZingLix #5 这个我完全赞同,C++就是啥都往里加的缝合怪,C++11 以后,后置返回值都加进去了,`auto sum(int a, int b)->int`,又前又后,以前 C 的协调性也丢的差不多了。
    richard1122
        9
    richard1122  
       2020-06-03 18:54:51 +08:00 via Android
    进一步的话我觉得 f# 挺不错
    mirrorman
        10
    mirrorman  
       2020-06-03 19:03:04 +08:00
    C++比较难得的是缝合了这么多年向后兼容以及语法大致上的一体性还凑活,要是早扔掉兼容 C 去设计肯定不是这个鸟样子,动不动就加关键字我真是吐了,但反早出点 Attribute 之类的设计(不是编译器厂商私活的 attr )也比现在这么多关键字和复杂的规则强
    secondwtq
        11
    secondwtq  
       2020-06-03 19:15:14 +08:00 via iPhone
    不好写 parser ……
    Fule
        12
    Fule  
       2020-06-03 21:55:04 +08:00   ❤️ 4
    你是说这样的?
    `Dim str As String`
    😁
    XIVN1987
        13
    XIVN1987  
       2020-06-04 09:00:58 +08:00
    我觉得还是 C++的方式比较好:
    int i = 1;
    auto i = 1;
    不管是写 int 还是写 auto 都在一个位置,,一致性更好
    liaojl
        14
    liaojl  
       2020-06-04 10:10:22 +08:00
    类型后置有个问题,就是 IDE 不能自动补全变量名。
    ```
    person Person //变量 person 要自己全部打出来
    ```
    如果是后置的话
    ```
    Person person; //person 打出第一个字母 p 的时候 IDE 就已经可以补全 person 了
    ```
    aguesuka
        15
    aguesuka  
       2020-06-04 12:21:20 +08:00 via Android
    因为我喜欢 golang
    又因为 golang 是类型后置的语言
    所以我喜欢的语言是类型后置的语言
    我喜换类型后置的语言
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   960 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 20:35 · PVG 04:35 · LAX 13:35 · JFK 16:35
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.