前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >为何 Go 的声明语法有点怪?(语法比较)

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

作者头像
Dylan Liu
发布2019-07-01 11:59:57
1.4K0
发布2019-07-01 11:59:57
举报
文章被收录于专栏:dylanliudylanliudylanliu

摘要

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 必须用括号括起来,以表明这是一个函数指针,如果我们有一个函数指针的参数呢?

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 语言的语法上,一些其他的语言像函数式编程语言,声明式编程语言的编程思想对我们也会有很大的启发,多涉猎一下,对我们思考问题的思路会有很大的启发作用。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 摘要
  • C 语法
  • Go 语法
  • Haskell 语法
  • 一点思维发散
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档