Go 语言设计得还真敷衍
2015 1 2 03:01 AM 2911次查看
可看到近来 Go 的好评如潮,我还是忍不住又重新学习了一遍。
给我的感觉是几乎每一个语法都没有好好思考,只在某些时候看上去不错,但另一些时候则非常不优雅。
下面就举些例子吧,大部分都记不得了,只能一边重温一边找。
类型放在标识符后面
为了体现与 C 的不同,特意把顺序反过来,看上去很酷,不过不太直观:
var x int = 1
var a, b, c int
而且这种不一致性是为什么:
var (
a = 1
b int = 2
c bool = true
) // 正常
var a, b int, c bool = 1, 2, true // 语法错误
函数的返回值位置总觉得有点怪,而且为什么参数列表这样写又支持了:
func add(a, b int, sign bool) int {
if !sign {
b = -b
}
return a + b
}
再看到方法,知道为什么要这么怪了:
func (x Int) Add(y int) int {
return int(x) + y
}
返回值还能有多个和命名呢,那么多括号确实摆哪都有道理:
func (x Int) Add(y int) (sum int) {
sum = int(x) + y
return
}
首字母大写表示 public,小写表示 private
这条适用于包里的函数、结构体的字段和方法等。
我只呵呵,不评价。
array 和 slice
简单来说,前者是定长的,和 C 语言的数组一样;后者是不定长的,和 Python 的 list 一样。
可这乱七八糟的符号,我半天才发现是不一样的:
var array1 = [5]int{1, 2, 3, 4, 5}
var array2 = [...]int{1, 2, 3}
var slice = []int{1, 2, 3, 4, 5}
这三个玩意的类型分别是 [5]int、[3]int 和 []int。还有 [][]int、[8][]int、[][8]int 和 [8][8]int 这些变种呢,写个类型真严(蛋)谨(疼)。
map
终于不能像 array 和 slice 一样只用符号了,得引入一个 map 关键字来声明:
var m map[string]int
要是 value 比较复杂,会不会很头晕:var m map[string]map[string][]string
类型转换
bool 和整型之间不能进行类型转换:
var a = int(true)
var b = bool(1)
// 以上都会出错
array 和 slice 转换时要注意元素类型:
var array = [...]int{1, 2, 3}
var slice1 []int
slice1 = array[:]
var slice2 []int32
slice2 = array[:] // 报错
var slice3 []int64
slice3 = array[:] // 还是报错
就是这么严谨。自定义类型和原始类型不能混用:
type Int int
func (self Int) Add (another int) int {
// return self + another
// return self + Int(another)
// 以上都是错的,以下才是对的
// return int(self + Int(another))
return int(self) + another
}
interface
Go 的接口和很多语言都不一样,一个类型可能是被动实现接口的。
比如上面我给 Int 实现了一个 Add 方法,如果我再加一个接口的定义:
type Number interface {
Add(int) int
}
Int 就莫名其妙地实现了 Number 接口了,我在任何声明需要用 Number 的地方,都可以用 Int 类型的对象了。可一旦未来我的 Number 接口需要变化,比如增加一个 Sub 方法,所有的调用者和实现者好像就傻眼了……
而且这个接口用来实现泛型有点尴尬,比如 Go 的排序甚至比 C++ 和 Java 还麻烦。
具体实现可以看 sort 包里的例子。
需要对排序的 slice(例如 []int)定义一个类型(例如 T),再给这个类型实现 3 个方法,然后将这个 slice 转换成 T 类型,最后传给 sort.Sort 方法。
当然,其中一方面是因为没法直接给 []int 定义方法,另一方面则是这里本该用传递函数来实现。
另外,接口也能进行类型转换:
type A interface {}
type B interface {
Test()
}
var a A
var b = B(a) // 编译期错误
var b = a.(B) // 运行时错误
又引入一个蛋疼的语法,为什么 a.B 和 a.(B) 不是一回事?此外,Go 里面可以用 interface{} 这种空接口来替代任意类型,有点 void* 的意味。可是需要传递 []interface{} 的地方,却不能传递 []int 这种具体的类型。
例如:
package main
import "fmt"
func first(slice []interface{}) interface{} {
return slice[0]
}
func main() {
s := []int{1, 2, 3} // 报错
f := first(s)
fmt.Println(f)
}
一种办法是修改声明:s := []interface{}{1, 2, 3}
另一种办法就是转换每一个元素:func main() {
s1 := []int{1, 2, 3}
s2 := make([]interface{}, 3)
for i, v := range s1 {
s2[i] = v // 这里可以不用写成 interface{}(v)
}
f := first(s2)
fmt.Println(f)
}
优雅得无法直视。差点忘了 Go 还支持可变参数,例如 fmt.Sprintf 的函数签名是这个:
func Sprintf(format string, a ...interface{}) string
那个 a ... 就是可变参数了,本身是一个 slice,所以在上文中应该是 []interface{} 类型。于是来实现一个输出 IP 地址的方法:
type IPAddr [4]byte
func (ip IPAddr) String() string {
return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])
}
这样当然不优雅,如果是用 Python,直接 *ip 就行了,Go 里面本来也应该这样:
func (ip IPAddr) String() string {
return fmt.Sprintf("%d.%d.%d.%d", ip ...)
}
但是类型不符,必须这样写:func (ip IPAddr) String() string {
addr := make([]interface {}, 4)
for i, v := range ip {
addr[i] = v
}
return fmt.Sprintf("%d.%d.%d.%d", addr ...)
}
我当时那心情,简直就和元首一样。指针
Go 的指针和 C 的指针比较像,* 和 & 的用法基本一致。
一个奇怪的不一致处是 (*x).y,当 y 是 *x 的一个属性时,可以简写为 x.y(C 是用 x->y);而当 y 是 *x 的一个方法时,就不能这样缩写了。
原因是 T 和 *T 是不同的类型,给类型 T 定义的方法,是不能用在 *T 的变量上的;而 *T 的方法也不能用在 T 的变量上。
channel
Go 使用 channel 来在 goroutines 之间通信,并在阻塞时切换 goroutine。
虽然类型声明难看点,不过 <- 看上去还算直观
var ch chan int = make(chan int, 10)
ch <- 1
ch <- 2
ch <- 3
ch <- 4
可是接下来是什么鬼:<-ch
x := <-ch
y, z := <-ch, <-ch
其他诸如错误处理之类的我就不说了。总体而言,Go 的语法给我的感觉是设计初期考虑不周,后期胡乱地缝补,导致一致性不强。
至于优势,据说只有部署方便(仅依赖 glibc)和并发编程这两点。
向下滚动可载入更多评论,或者点这里禁止自动加载。