为什么 Go 的声明语法有点怪?(语法比较)

摘要

Go 语法对第一次接触 Go 的新手来有点怪,由于你们习惯了类 C 语法将类型放在前面的方式,对 Go 将类型放在参数后面有点不习惯,刚开始感受很别扭,那 Go 设计者是基于什么考量才设计成这样呢?这里咱们比较一下 C,Go,Haskell 三者的语法,能够看到其实语言的语法其实都是服务于本身的设计目标的。编程

C 语法

咱们先来看一下 C 语法,从大学出来的通常刚开始就是接触的 C,培训出身的刚开始接触的应该是 Java,不过这二者在声明语法上基本一致(固然 Java 简化了不少,像指针就没了),咱们就以 C 来看,毕竟 Go 号称新世纪的 C 语言。数组

简单声明:编程语言

int x;

这里咱们将类型放在左边,在右边是一个表达式,所以咱们声明指针和数组这样写:函数式编程

int *p;
int x[3];

这里*p 的类型是int,x 是一个int类型的数组,x[3] 的类型是int。函数

函数也遵循这个基本的结构学习

int foo(int x)
int foo2(char *arg[])

这是一个很聪明的结构,对于简单类型来讲,可是当类型变得复杂后,这个语法就会变得让人迷惑,得费点工夫才能看明白。设计

声明一个函数指针:指针

int (*fp) (int a, int b);

这里 *fp 必须用括号括起来,以代表这是一个函数指针,若是咱们有一个函数指针的参数呢?code

int (*fp)(int (*fp1) (int a), int b);

这已经变得很是难看懂了,至少在第一眼的时候你看不懂,无论你怎么加空格,若是你以为还好的话,那咱们的返回值是一个函数指针呢?继承

int (*(*fp)(int (*)(int, int), int))(int, int)

嗯,反正我已经看不懂了。

Java 里没有函数指针,只有使用接口,这大大简化了类型声明的复杂度,并且 Java 的数组声明也和 C 不同,为了保持清晰度,Java 将中括号挪到了类型后面 int[] a, 而不是跟 C 同样 int a[] 将参数放在中间。

Go 语法

Go 将类型放到了后面,咱们与 C 比对一下就能发如今复杂状况下 Go 仍是能保证基本的类型清晰度。

基本声明

x int
p *int
a [3]int

p 就是一个int类型的指针,不存在第二种写法,数组也很明确的是类型的一部分。

看下函数:

func foo(a int, b *int) string

这和 C 感受也没有多大的差异,并且从左向右读起来也很顺畅。

参数是函数和返回值是参数的状况呢?

func foo(func(int, int), int) func(float, []int) string

仍是很是清晰,从左到右须要的参数和返回值都是一目了然的。

想要说明的一点是数组和指针的使用是和 C 同样的,咱们获取数组某个位置的值和指针指向的值:

x := a[1]
int t = *p

声明和使用中括号和星号的位置反过来了,数组的使用是从 C 继承过来的, 指针的星号放在前面也是为了避免和乘号的星号混淆,不过这样咱们有时候在使用的时候也避免不了括号。

在我看来,这种状况下不如直接换一个符号来获取指针所指向地址的值,由于星号已经有了两种语义,编译器须要根据上下文来判断星号表明的具体含义。我扫视键盘,以为@ 符号甚好,语义和含义都符合取值的要求,只是不知道语言做者在设计的时候为何没有考虑好,多是这个符号没人用过,他们也就瓜熟蒂落的沿袭了 C 的语法吧。

Haskell 语法

Haskell 做为一门纯函数式编程语言,大部分人可能听过,可是接触过、学习过的人应该不会太大,毕竟日常工做用不到,我也只是简单的了解过,里面的一些函数式理念对于写出更复用的函数有很强的启发做用,建议你们去了解一下。

Haskell 的语法是与自身为纯函数式的编程语言分不开的,Haskell 不使用括号这种具备边界性质的符号来界定参数,而是使用 -> 开放形式来声明,返回值与入参同样,都是用-> 串起来的,使得声明看起来很是的一致。

Haskell 是强类型语言,可是带了一个很强大的类型推导系统,咱们在声明变量时不须要指定变量的类型,编译器会根据初始化数据或函数返回值等来判断参数类型,另外一方面,Haskell是函数式编程语言,咱们声明的类型都是 immutable 的,咱们看不到 int a 的状况。

OK, 咱们如今来声明一个函数:

inc :: Int -> Int
inc x = x + 1

注:在 Haskell 里,函数是一等公民,这里我将函数的声明类型也写出来只是为了清晰起见,其实咱们能够简单只写inc x = x + 1, Haskell 自动推断出相关类型。

咱们的入参是一个整数,返回值也是一个整数,从左到右很清晰,若是咱们的入参、返回值是函数如何呢?写一个函数式编程里经常使用的filter

filter :: (a -> Bool) ->[a] -> [a]
filter _ [] = []
filter f (x:xs) 
     | f x = x : filter f xs
     | otherwise = filter f xs

咱们使用括号来界定一个函数,代表这是一个总体,返回值也同样,只须要在后面加上括号就能够了,能够看到也是很是清楚明白的。

Haskell 为何要这样设计? 这和 Haskell 语言的函数式本质是分不开的。函数式里面有一个术语叫柯里化,柯里化后的函数能够一次只接收一个参数,每次返回一个新的函数,直到全部的参数都知足了,才会触发计算返回最终值,而 Haskell 里的函数默认是所有柯里化的,譬如咱们想过滤出列表里全部偶数,咱们能够这样写:

list1 = filter even a
list2 = filter even b

这里a/b都是列表,你有没有发现filter even 咱们写了两边,秉持DRY原则,咱们能够将它抽出来变成一个函数:

filterEven = filter even
list1 = filterEven a
list2 = filterEven b

咱们只对filter 提供一个参数,返回值是一个接收一个list参数的函数,咱们就能够复用咱们新的函数了。 回过头来咱们再看一下 Haskell 的函数声明语法a -> b -> c,其实这里面没有什么入参、返回值的区别,函数从左到右接收参数,返回值就是最后参数后面的部分,也就是说咱们提供了一个参数a,返回就是b -> c, 是否是很熟悉,这就是一个函数,咱们能够按正常的函数来使用,由于它于正常函数的声明是如出一辙的。

一点思惟发散

昨天(2018.09.26)在路上走着忽然又想起来这个,C 语言的声明语法可类比中国人的姓名,而 Go语言的声明语法可类比美国人的名姓。中国人的先姓后名致使通常孩子随父亲姓的话,不太可能将妈妈的姓也加进来,好比魏随风,加入另外一个姓变成魏张随风,魏马随风很奇怪,美国人的名字后面能够加任意多的姓,Anderson Ma Li Zhang,并且也相对清晰。

总结

各个语言在设计时总要当心的考虑本身的声明语法,要使它符合本身的设计目标,同时语法又要尽量的简单、清晰、易用,Go 在 C 语法上的基础上作了一点改进,就让一些复杂状况变得清晰了,可见也是下了很大功夫的。同时咱们也不要仅仅局限在类 C 语言的语法上,一些其余的语言像函数式编程语言,声明式编程语言的编程思想对咱们也会有很大的启发,多涉猎一下,对咱们思考问题的思路会有很大的启发做用。

相关文章
相关标签/搜索