标签 Haskell 下的文章

也谈Go语言编程 – Hello,Go!

Go is expressive, concise, clean, and efficient. Its concurrency mechanisms make it easy to write programs that get the most out of multicore and networked machines, while its novel type system enables flexible and modular program construction. Go compiles quickly to machine code yet has the convenience of garbage collection and the power of run-time reflection. It's a fast, statically typed, compiled language that feels like a dynamically typed, interpreted language.
                                                                                          – 摘自Go语言官方站点

对于一门编程语言最深刻的喜欢莫过于对这门编程语言的设计理念的认同了,Go语言是继C语言之后又一门让我有如此感觉的编程语言。

初听到这门语言的存在时,我皱了皱眉:怎么起了这么一个名字!绝大多数编程语言都以名词或人名命名(如C、Java、PythonRubyHaskell、Ada等),而这门语言却用了一个日常生活中使用极为频繁的动词Go作为名字,这似乎有些太大众化了。不知为何,这个名字总是让 我联想到以前中国农村给小孩子常起的几个名字:二狗、牛娃等^_^。况且之前已经有很多IT产品也以Go作为名字(例 如,Thoughtworks公司出品的敏捷管理工具也叫Go)。

不过随着对这门语言的了解的深入,名字已不再是问题了。Go语言对我这个C程序员产生了强大的吸引力,原因如下:

* Go保持了与C语言一脉相承的理念:短小精悍、致力于成为系统编程语言、简洁而熟悉的C语言家族语法、静态编译型语言、保留了指针、运行高效;
* Go填平了C语言与生俱来的为数不少的"坑";
* Go提升了编译速度,统一了源码组织、构建规范以及编码规范,让程序员更集中精力于问题域;
* Go改进了并发模型,在语言级别原生支持多核平台;
* Go语言起点高,以创新的设计以及甚小的代价兼容了现有主流编程范型(例如OO等)。

因此有人称Go为21世纪的C语言,我觉得不为过。从这篇文章开始,我将和大家一起走入Go语言的世界。

一、安装Go

Go语言官方站(从国内访问十分不稳定,时能时不能,原因你懂的)对Go安装有着较为详尽的说明。如果你使用的是Linux、Mac OS或Windows,那你应该可以很顺利地完成Go的安装。Go于今年上旬发布了第一个稳定版本Go 1,目前最新版本是1.0.2,可以从Google Code上的Go项目中下载。我的环境为Ubuntu 10.04 32-bit,下载go1.0.2.linux-386.tar.gz后,解压到/usr/local/go下面:

$ ls /usr/local/go
api/     bin/           doc/        include/  LICENSE  PATENTS    README        src/   VERSION
AUTHORS  CONTRIBUTORS  favicon.ico  lib/      misc/    pkg/    robots.txt  test/

然后将/usr/local/go/bin添加到你的PATH环境变量中,你就可以在任意目录下执行go程序了:

$ go version
go version go1.0.2

如果你得到上面的输出结果,可以断定你的Go安装成功了!

二、第一个Go程序 – Hello, Go!

我们建立一个用于编写Go程序的工作目录go-examples,其绝对路径为/home/tonybai/go-examples。好了,开始 编写我们的第一个Go程序。

我们在go-examples下创建一个文件hellogo.go,其内容如下:

package main

import (
    "fmt"
)

func main() {
    fmt.Printf("Hello, Go!\n")
}

下面我们来编译该源文件并执行生成的可执行文件:

$ go build hellogo.go
$ ls
hellogo*  hellogo.go
$ hellogo
Hello, Go!

通过go build加上要编译的Go源文件名,我们即可得到一个可执行文件,默认情况下这个文件的名字为源文件名字去掉.go后缀。当然我们也 可以通过-o选项来指定其他名字:

$ go build -o myfirstgo hellogo.go
$ ls
myfirstgo*  hellogo.go

如果我们在go-examples目录下直接执行go build命令,后面不带文件名,我们将得到一个与目录名同名的可执行文件:

$ go build
$ ls
go-examples*  hellogo.go

三、程序入口点(entry point)和包(package)

Go保持了与C家族语言一致的风格:即目标为可执行程序的Go源码中务必要有一个名为main的函数,该函数即为可执行程序的入口点。除此之外 Go还增加了一个约束:作为入口点的main函数必须在名为main的package中。正如上面hellogo.go源文件中的那样,在源码第 一行就声明了该文件所归属的package为main。

Go去除了头文件的概念,而借鉴了很多主流语言都采用的package的源码组织方式。package是个逻辑概念,与文件没有一一对应的关系。 如果多个源文件都在开头声明自己属于某个名为foo的包,那这些源文件中的代码在逻辑上都归属于包foo(这些文件最好在同一个目录下,至少目前 的Go版本还无法支持不同目录下的源文件归属于同一个包)。

我们看到hellogo.go中import一个名为fmt的包,并利用该包内的Printf函数输出"Hello, Go!"。直觉告诉我们fmt包似乎是一个标准库中的包。没错,fmt包提供了格式化文本输出以及读取格式化输入的相关函数,与C中的printf或 scanf等类似。我们通过import语句将fmt包导入我们的源文件后就可以使用该fmt包导出(export)的功能函数了(比如 Printf)。

在C中,我们通过static来标识局部函数还是全局函数。而在Go中,包中的函数是否可以被外部调用,要看该函数名的首母是否为大写。这是一种 Go语言固化的约定:首母大写的函数被认为是导出的函数,可以被包之外的代码调用;而小写字母开头的函数则仅能在包内使用。在例子中你也看到了 fmt包的Printf函数其首母就是大写的。

四、GOPATH

我们把上面的hellogo.go稍作改造,拆分成两个文件:main.go和hello.go。

/* hello.go */
package hello

import "fmt"

func Hello(who string) {
    fmt.Printf("Hello, %s!\n", who)
}

/* main.go */
package main

import (
    "hello"
)

func main() {
    hello.Hello("Go!")
}

用go build编译main.go,结果如下:

$ go build main.go
main.go:4:2: import "hello": cannot find package

编译器居然提示无法找到hello这个package,而hello.go中明明定义了package hello了。这是怎么回事呢?原来go compiler搜索package的方式与我们常规理解的有不同,Go在这方面也有一套约定,这里面涉及到一个重要的环境变量:GOPATH。我们可以使用go help gopath来查看一下有关gopath的manual。

Go compiler的package搜索顺序是这样的,以搜索hello这个package为例:

* 首先,Go compiler会在GO安装目录(GOROOT,这里是/usr/local/go)下查找是否有src/pkg/hello相关包源码;如果没有则继续;
* 如果export GOPATH=PATH1:PAHT2,则Go compiler会依次查找是否存在PATH1/src/hello、PATH2/src/hello;配置在GOPATH中的PATH1和PATH2被称作workplace;
* 如果在上述几个位置均无法找到hello这个package,则提示出错。

在本例子中,我们尚未设置过GOPATH环境变量,也没有建立类似PATH1/src/hello这样的路径,因此Go compiler显然无法找到hello这个package了。我们来设置一下GOPATH变量并建立相关目录:

$ export GOPATH=/home/tonybai/go-examples
$ mkdir src/hello
$ mv hello.go src/hello
$ go build main.go
$ ls
main*  main.go    src/
$ main
Hello, Go!

五、Go install

我们将main.go移到src/main中,这样这个demo project显得更加合理,所有源码均在src下:

$cd src
$ ls
hello/    main/

Go提供了install命令,与build命令相比,install命令在编译源码后还会将可执行文件或库文件安装到约定的目录下。我们以main目录为例:

$ cd main
$ go install

install命令执行后,我们发现main目录下没有任何变化,原先build时产生的main可执行文件也不见了踪影。别急,前面说过Go install也有一套自己的约定:
* go install(在src/DIR下)编译出的可执行文件以其所在目录名(DIR)命名
* go install将可执行文件安装到与src同级别的bin目录下,bin目录由go install自动创建
* go install将可执行文件依赖的各种package编译后,放在与src同级别的pkg目录下

现在我们来看看bin目录:
$ ls /home/tonybai/go-examples
bin/  src/ pkg/
$ ls bin
main*

的确出现一个bin目录,并且刚刚编译的程序main在bin下面。

hello.go编译后并非可执行程序,在编译main的同时,由于main依赖hello package,因此hello也被关联编译了。这与单独在hello目录下执行install的结果是一样的,我们试试:

$ cd hello
$ go install
$ ls /home/tonybai/go-examples
bin/  pkg/  src/

在我们的workspace(go-examples目录)下出现了一个pkg目录,pkg目录下是一个名为linux_386的子目录,其下面有一个文 件:hello.a。这就是我们install的结果。hello.go被编译为hello.a并安装到pkg/linux_386目录下了。

.a这个后缀名让我们想起了静态共享库,但这里的.a却是Go独有的文件格式,与传统的静态共享库并不兼容。但Go语言的设计者使用这个后缀名似乎是希望 这个.a文件也承担起Go语言中"静态共享库"的角色。我们不妨来试试,看看这个hello.a是否可以被Go compiler当作"静态共享库"来对待。我们移除src中的hello目录,然后在main目录下执行go build:

$ go build
main.go:4:2: import "hello": cannot find package

Go编译器提示无法找到hello这个包,可见目前版本的Go编译器似乎不理pkg下的.a文件。http://code.google.com/p/go/issues/detail?id=2775 这个issue也印证了这一点,不过后续Go版本很可能会支持链接.a文件。毕竟我们在使用第三方package的时候,很可能无法得到其源码,并且在每个项目中都保存一份第三方包的源码也十分不利于项目源码的后期维护。

六、像脚本一样运行Go源码

Go具有很高的编译效率,这得益于其设计者对该目标的重视以及设计过程中细节方面的把控,当然这不是本文要关注的话题。正是由于go具有极速的编译,我们才可以像使用运行脚本语言那样使用它。

目前Go提供了run命令来直接运行源文件。比如:

$ go run main.go
Hello, Go!

go run实际上是一个将编译源码和运行编译后的二进制程序结合在一起的命令。但目前go源文件尚不支持作成Shebang Script,因为Go compiler尚不识别#!符号,下面的源码文件运行起来会出错:

#! /usr/local/go/bin/go run

package main

import (
    "hello"
)

func main() {
    hello.Hello("Go!")
}

$ go run main.go
package :
main.go:1:1: illegal character U+0023 '#'

不过我们可以可借助一些第三方工具来运行Shebang Go scripts,比如gorun

七、测试Go程序

前面说过Go起点较高,因此其自身就提供了一个轻量级单元测试框架包以及运行测试集的命令。

我们用一个例子来说明如何编写包的测试代码以及如何运行这个测试。我们在go-examples/src下建立另外一个目录mymath,mymath目录下mymath包的代码如下:

/* mymath.go */
package mymath

func MyAdd(i int, j int) int {
    return i + j
}

要对mymath包进行测试,我们需在同一目录下创建mymath_test.go文件,其中对MyAdd函数的测试代码如下:

/* mymath_test.go */
package mymath

import "testing"

func TestMyAdd(t *testing.T) {
    a, b := 4, 2
    if x := MyAdd(a, b); x != 6 {
        t.Errorf("MyAdd(%d, %d) = %d, want %d", a, b, x, 6)
    }
}

在这个文件中我们import了Go提供的标准单元测试包-testing,并且每个测试方法都已Test作为前缀开头。现在我们来运行一下这个测试,在mymath目录下运行go test命令:

$ go test
PASS
ok      mymath    0.007s

如果用例出错,我们就可看到下面提示:

$go test
— FAIL: TestMyAdd (0.00 seconds)
    mymath_test.go:8: MyAdd(4, 2) = 6, want 6
FAIL
exit status 1
FAIL    mymath    0.007s

由上可以看出,Go test也有自己的一些约定:测试源文件的名字必须以_test.go作为结尾;测试代码与被测代码在同一个包中;测试代码要导入testing包;测试 函数要以Test作为前缀,并且测试函数的函数签名必须是这样的:func TestXXX(t *testing.T)。

语言自带对测试的支持的好处是一致性,避免了大家使用不同的测试框架而给阅读、交流和维护带来的不便。

八、项目源码组织

有了源码、有了对编译原理的理解、有了测试框架的支持,我们就可以策划项目源码组织形式了。不过Go的诸多约定基本上已经将我们限制在如下结构上:

proj1/
    bin/
        myapp1*
    pkg/
        linux_386/
            lib1.a
            lib2.a
    src/
        lib1/
            lib1.go     
            lib1_test.go
        lib2/
            lib2.go     
            lib2_test.go
        … …
        myapp1/
            main.go       # main package source
            main_test.go  # test source

proj2/
    bin/
        myapp2*
    pkg/
        linux_386/
            lib3.a
            lib4.a
    src/
        lib3/
            lib3.go     
            lib3_test.go
        lib4/
            lib4.go     
            lib4_test.go
        … …
        myapp2/
            main.go       # main package source
            main_test.go  # test source

基于上述结构,我们可将GOPATH设置为proj1_path:proj2_path

九、代码风格(coding style)

Go程序员可以不再纠结于到底使用哪种代码风格,因为Go已经将代码风格做了严格的约定,一旦违反,Compiler直接给出Error。go还提供了fmt命令来协助Go程序员按标准格式化源文件。

从上面例子中我们可以看到Go的几大风格特点是:

* 左大括号'{'一定在函数名或if等语句在同一行
   func foo {

   }

* 无需显式用分号;将语句分隔(除非是在一行写上多条语句),因为compiler会替大家在适当位置加入分号的。
   i, j := 2, 3
   MyAdd(i, j)

   if x := MyAdd(a, b); x != 6 {
            … …
   }

* if、for等后面的表达式无需用小括号括上
  
   if x != 5 {
            … …
   }

十、查看文档

Go的全量文档几乎与Go安装包一起发布。安装Go后,执行godoc –http=:端口号即可启动doc server。打开浏览器,输入http://localhost:端口号即可以看到几乎与Go官方站完全相同的文档页面。

十一、参考书籍

Go毕竟是新生代语言,其自身尚不成熟和完善,资料也较少。这里推荐两本市面上比较好的且内容已更新到Go 1的书籍:

* Mark Summerfield的《Programming in Go: creating applications for the 21st century
* Ivo Balbaert的《The Way to Go – A Thorough Introduction to the Go Programming Language

翻译《七周七语言》的那些事儿

今天在互动出版网看到《七周七语言:理解多种编程范型》一书已经开卖了。看到自己参与翻译的第一本书出版了,心中还是很愉悦的,因为自己的辛苦付出终于有了结果。

一、缘起

能够参与到这本书的翻译完全是机缘巧合。记得2011年初我启动了一个《Programming in Haskell》的公共翻译项目,可是由于欠缺版权的考虑,中途不得不终止了该书的翻译。当时经dreamhead介绍联系到图灵刘江总编,希望人邮能 引进版权以促成该书的翻译,但刘总编考虑到该书是有关Haskell这门"小众"语言的,引进后受众面小,书很可能卖不出去,商业价值不高(后得知该书作 者Graham Hutton博士已经在与某出版社谈中文版版权的事宜了,并已经委托其一位同事进行中文版的翻译工作了)。不过刘总编说图灵当时已经引进了《Seven Languages in Seven Weeks》一书的中文版权,但第一译者戴玮因工作学习繁忙,可能无法按期完成全部翻译,问我是否愿意参与翻译。我的最初目标就是翻译一本英文技术书籍, 有这样的机会,而且书还可以在国内出版,于是我就欣然接下了这个翻译工作。

二、翻译过程

经过试译审核,顺利与图灵签订了翻译合同,我将负责翻译该书的PrologScalaHaskell三个章节。正式翻译是在2011年春节后开始的, 为了能在合同规定的第一个时间点交稿,我连续N天翻译到凌晨下半夜,工作日中午午休时间也在抓紧时间翻译,周末也不放松。因为是第一次翻译,生怕自己翻译 的不好,于是对原书中的每句话都字斟句酌,仔细揣摩。另外虽说此书是一本技术书籍,但作者给每门编程语言都赋予了一部电影中的典型人物角色,并用电影中的 情节或人物角色的特征作为章节的导引,这使得每章的开篇十分难于翻译,特别是当我不熟悉语言所对应的那部影片中的那个角色时,翻译更是举步维艰。为此,我 特意看了一遍"雨人(Rain Man)"和"星际旅行(Star Trek)",重温了"剪刀手爱德华(Edward Scissorhands)",为的就是能够更精确地定位本书作者所要表达的意思。Scala一章的第一稿提交后,我收到了图灵编辑不错的反馈。于是再接 再励,在2011年4月末交了全部初稿,5月中旬完成了中耕校对;2012年3月份完成排后稿的校对。

三、关于翻译方法和心得

这是我第一次参与翻译项目,说实在的真没有资格谈什么翻译方法,我也不是什么专业翻译人员。但在这本书的翻译过程中还是有若干经验和教训可以与大家分享的。

* 心态

我认识的参与过技术书籍翻译的朋友都说:翻译不是为了赚钱(那些以翻译为谋生手段的职业翻译除外),这点我深表认同。翻译工作是一件枯燥、辛苦甚至是费力 不讨好(出版后可能被拍砖)的工作。因此翻译前就要摆正心态,弄清楚自己为何要翻译,有了良好心态,才会有持续不断的动力,否则译着译着人就容易产生懈 怠,进度和质量都会下降,你需要这样一种战胜懈怠并持续下去的手段。

* 你是翻译质量的决定者

不要过于期望诸多编辑朋友会拿出百分百的时间对你的翻译内容进行校对,出版社的编辑们太忙了,一个人估计要至少负责10本以上书籍的出版工作,因此你才是翻译质量的决定者,从开始翻译的那一刻你就要保持高质量水准。

* 第一遍就要保持高质量,不要期待你能回头做二次翻译

第一遍翻译时,务必保证按顺序逐字逐句的高质量的翻译,一次到位;遇到难点也不要跳过,而是要集中精神搞定这个难点;否则你就会发现你积蓄的难点越来越 多,严重影响你后续翻译的情绪和心理。不要有回头做全面二次翻译的想法,因为你会发现那基本不可行,二次翻译时你会发现你的思路严重受制于第一次翻译的思 路,因此不仅不会提高什么质量,还会使你变得更加烦躁,严重影响翻译进度。

* 前后一致

保持前后章节的术语、句型等翻译的一致性。这点在翻译和校对时都要重点关注。

* 除了认真还是认真

不是所有人都是翻译天才,大部分译者,特别是技术书籍的译者,可能只是那个领域的从业人员(比如我),在翻译能力上存在不足。但万事就怕认真,认真可以尽量袮补在能力上不足,也是出品高质量译文的必要条件。

四、关于《七周七语言》一书

从本书的中文名字,你也许会将其与"21天学会C语言"之类的捷径书籍混为一谈,但本书的初衷与那些捷径书籍显然不同。本书意在让你在短时间内了解到多种 编程语言的范式和主要特性,并做简单的对比了解。书的作者也许并不期望你在看完某种语言后就彻底学会了这门语言,那显然不是本书的意图。如今也许是另一个 编程语言百家争鸣的时代,新的语言层出不穷,作者试图帮助大家在如此繁多的语言当中找到一些适合你投资、学习和使用的有前途的编程语言。

书的最终版本我也没有拿到,我也只是看了我所翻译的那三章,因此书的内容好坏我也不能妄加评论。这里是Amazon.com上关于此书的一些书评(中文翻译版),另外从本书获得了2011年Jolt大奖可以看出本书还是被业内专家一致看好的。

个人感觉出版的有些晚了,如果能与Jolt大奖的公布同步推出也许效果会更好。书的最终纸质版本我也没有拿到,尚不知书的印刷质量如何,另外翻译的质量如何还得需要大家评判。

最后十分感谢翻译过程刘江、杨海玲 、傅志红、李松峰、丁晓昀等各位老师对我的帮助。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats