Golang的演化歷程

tonybai.com發表於2014-10-30

  本文來自Google的Golang語言設計者之一Rob Pike大神在GopherCon2014大會上的開幕主題演講資料“Hello, Gophers!”。Rob大神在這次分 享中用了兩個生動的例子講述了Golang的演化歷程,總結了Golang到目前為止的成功因素,值得廣大Golang Programmer & Beginner學習和了解。這裡也用了”Golang的演化歷程”作為標題。

  1、Hello Gophers!

package main

import "fmt"

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

  Rob大神的見面禮,後續會有針對這段的演化歷史的陳述。

  2、歷史

  這是一個歷史性的時刻。

  Golang已經獲得了一定的成功,值得擁有屬於自己的技術大會。

  3、成功

  促成這份成功的因素有許多:

  – 功能
  – 缺少的功能
  – 功能的組合
  – 設計
  – 人
  – 時間

  4、案例學習:兩段程式

  我們來近距離回顧兩段程式。

  第一個是你見過的第一個Go程式,是屬於你的歷史時刻。

  第二個是我們見過的第一個Go程式,是屬於全世界所有Gophers的歷史時刻。

  先看第一個“hello, world”

  5、hello.b

main( ) {
    extrn a, b, c;
    putchar(a); putchar(b); putchar(c); putchar('!*n');
}
a 'hell';
b 'o, w';
c 'orld';

  上面這段程式碼首先出現在1972年Brian W. Kernighan的B語言教程中(也有另外一說是出現在那之前的BCPL語言中)。

  6、hello.c

main()
{
    printf("hello, world");
}

  上面這段程式碼出現在1974年Brian W. Kernighan編寫的《Programming in C: A Tutorial》中。這份教程當時是作為Unix v5文件的一部分。

  7、hello.c

main()
{
    printf("hello, world\n"); //譯註:與上面的hello.c相比,多了個換行符\n輸出
}

  這段程式碼首次出現在1978年Brian W. Kernighan和Dennis M. Ritchie合著的《The C Programming Language》一書中。

  8、hello.c, 標準C草案

#include <stdio.h> //譯註:與上面hello.c相比, 多了這個標頭檔案包含

main()
{
    printf("hello, world\n");
}

  這段程式碼出現在1988年Brian W. Kernighan和Dennis M. Ritchie合著的《The C Programming Language》第二版一書中,基於標準C草案。

  9、hello.c,標準C89

#include <stdio.h>

main(void) //譯註:與上面hello.c相比,多了個void
{
    printf("hello, world\n");
}

  這段程式碼出現在1988年Brian W. Kernighan和Dennis M. Ritchie合著的《The C Programming Language》第二版第二次修訂中。

  10、一兩代之後…

  (省略所有中間語言)

  關於Golang的討論開始於2007年年末。

  第一版語言規範起草於2008年3月份。

  用於實驗和原型目的的編譯器開發工作已經展開。

  最初的編譯器輸出的是C程式碼。

  語言規範一形成,我們就重寫了編譯器,輸出原生程式碼(機器碼)。

  11、hello.go, 2008年6月6日

package main

func main() int {
    print "hello, world\n";
    return 0;
}

  12、hello.go,2008年6月27日

package main

func main() {
    print "hello, world\n";
}

  當main函式返回,程式呼叫exit(0)。

  13、hello.go,2008年8月11日

package main

func main() {
    print("hello, world\n");
}

  print呼叫加上了括號,這時print是一個函式,不再是一個原語。

  14、hello.go,2008年10月24日

package main

import "fmt"

func main() {
    fmt.printf("hello, world\n");
}

  我們熟知並喜歡的printf來了。

  15、hello.go,2009年1月15日

package main

import "fmt"

func main() {
    fmt.Printf("hello, world\n");
}

  頭母大寫的函式名用作才是匯出的符號。

  16、hello.go, 2009年12約11日

package main

import "fmt"

func main() {
    fmt.Printf("hello, world\n")
}

  不再需要分號。

  這是在2009年11月10日Golang開發釋出後的一次重要改變。

  這也是當前版本的hello, world

  我們花了些時間到達這裡(32年!)

  都是歷史了!

  17、不僅僅有C

  我們從”C”開始,但Go與C相比有著巨大的不同。

  其他一些語言影響和貫穿於Go的設計當中。

  C: 語句和表示式語法
  Pascal: 宣告語法
  Modula 2, Oberon 2:包
  CSP, Occam, Newsqueak, Limbo, Alef:  併發
  BCPL: 分號規則
  Smalltalk: 方法(method)
  Newsqueak: <-, :=
  APL: iota

  等等。也有一些是全新發明的,例如defer、常量。

  還有一些來自其他語言的優點和缺點:

  C++, C#, Java, JavaScript, LISP, Python, Scala, …

  18、hello.go,Go 1版

  將我們帶到了今天。

package main

import "fmt"

func main() {
    fmt.Println("Hello, Gophers (some of whom know 中文)!")
}

  我們來深入挖掘一下,把這段程式碼做一個拆解。

  19、Hello, World的16個tokens

package
main
import
"fmt"
func
main
(
)
{
fmt
.
Println
(
"Hello, Gophers (some of whom know 中文)!"
)
}

  20、package

  早期設計討論的主要話題:擴充套件性的關鍵

  package是什麼?來自Modula-2等語言的idea

  為什麼是package?

  – 擁有編譯構建所需的全部資訊
  – 沒有迴圈依賴(import)
  – 沒有子包
  – 包名與包路徑分離
  – 包級別可見性,而不是型別級別
  – 在包內部,你擁有整個語言,在包外部,你只擁有包許可的東西。

  21、main

  一個C語言遺留風範盡顯之處

  最初是Main,原因記不得了。

  主要的包,main函式

  很特別,因為它是初始化樹(initialization tree)的根(root)。

  22、import

  一種載入包的機制

  通過編譯器實現(有別於文字前處理器。譯註:C語言的include是通過preprocessor實現的)

  努力使其高效且線性

  匯入的一個包,而不是一個識別符號(identifiers)集合(譯註:C語言的include是將標頭檔案裡的識別符號集合引入)

  至於export,它曾經是一個關鍵字。

  23、”fmt”

  包路徑(package path)只是一個字串,並非識別符號的列表。

  讓語言避免定義它的含義 – 適應性。(Allows the language to avoid defining what it means—adaptability)

  從一開始就想要一個URL作為一個選項。(譯註:類似import “github.com/go/tools/xxx)

  可以應付將來的發展。

  24、func

  一個關鍵字,用於引入函式(型別、變數、常量),易於編譯器解析。

  對於函式字面量(閉包)而言,易於解析非常重要。

  順便說一下,最初這個關鍵字不是func,而是function。

  小插曲:

Mail thread from February 6, 2008
From: Ken Thompson <ken@google.com>
To: gri, r
larry and sergey came by tonight. we 
talked about go for more than an hour. 
they both said they liked it very much.
p.s. one of larrys comments was “why isnt function spelled func?”

From: Rob Pike <r@google.com>
To: ken, gri
fine with me. seems compatible with ‘var’.
anyway we can always say, “larry said to call it ‘func’”

  25、main

  程式執行的起點。除非它不是。(譯註:main不是起點,rob大神的意思是不是指下列情形,比如go test測試包,在google app engine上的go程式不需要main)

  將初始化與正常執行分離,早期計劃之中的。

  初始化在哪裡發生的?(譯註:說的是package內的func init() {..}函式吧)

  回到包設計。(譯註:重溫golang的package設計思想)

  26、()

  看看,沒有void

  main沒有返回值,由執行時來處理main的返回後的事情。

  沒有函式引數(命令列選項通過os包獲取)

  沒有返回值

  返回值以及語法

  27、{

  用的是大括號,而不是空格(譯註:估計是與python的空格縮排對比)

  同樣也不是方括號。

  為什麼在括號後放置換行符(newline)?

  28、fmt

  所有匯入的識別符號均限定於其匯入的包。(All imported identifiers are qualified by their import.)

  每個識別符號要麼是包或函式的本地變數,要麼被型別或匯入包限定。

  對程式碼可讀性的重大影響。

  為什麼是fmt,而不是format?

  29、.

  句號token在Go中有多少使用?(很多)

  a.B的含義需要使用到型別系統

  但這對於人類來說非常清晰,讀起來也非常容易。

  針對指標的自動轉換(沒有->)。

  30、Println

  Println,不是println,頭母大寫才是匯出符號。

  知道它是反射驅動的(reflection-driven)

  可變引數函式

  引數型別是(…); 2010年2月1日變成(…interface{})

  31、(

  傳統函式語法

  32、”Hello, Gophers (some of whom know 中文)!”

  UTF-8編碼的原始碼輸入。字串字面量也自動是utf8編碼格式的。

  但什麼是字串(string)呢?

  首批寫入規範的語法規則,今天很難改變了。(blog.golang.org/strings)

  33、)

  沒有分號

  在go釋出後不久我們就去除了分號

  早期曾胡鬧地嘗試將它們(譯註:指得是括號)去掉

  最終接受了BCPL的方案

  34、}

  第一輪結束。

  旁白:還沒有討論到的

  – 型別
  – 常量
  – 方法
  – interface
  – 庫
  – 記憶體管理
  – 併發(接下來將討論)

  外加工具,生態系統,社群等。

  語言是核心,但也只是我們故事的一部分。

  35、成功

  要素:
  – 站在巨人的肩膀上(building on history)
  – 經驗之作(building on experience) 譯註:最初的三個神級語言設計者
  – 設計過程
  – 早期idea提煉到最終的方案中
  – 由一個小團隊專門集中精力做

  最終:承諾
  Go 1.0鎖定了語言核心與標準庫。

  36、另一輪

  讓我們看第二個程式的類似演化過程。

  37、問題:素數篩(Prime sieve)

  問題來自於Communicating Sequential Processes, by C. A. R. Hoare, 1978。

  “問題:以升序列印所有小於10000的素數。使用一個process陣列:SIEVE,其中每個process從其前驅元素輸入一個素數並列印它。接下 來這個process從其前驅元素接收到一個升序數字流並將它們傳給其後繼元素,這個過程會剔除掉所有是最初素數整數倍的數字。

  38、解決方案

  在1978年的CSP論文中。(注意不是Eratosthenes篩)

  這個優美的方案是由David Gries貢獻出來的。

  39、CSP

  在Hoare的CSP論文中:

[SIEVE(i:1..100)::
    p,mp:integer;
    SIEVE(i - 1)?p;
    print!p;
    mp := p; comment mp is a multiple of p;
*[m:integer; SIEVE(i - 1)?m →
    *[m > mp → mp := mp + p];
    [m = mp → skip
    ||m < mp → SIEVE(i + 1)!m
]   ]
||SIEVE(0)::print!2; n:integer; n := 3;
    *[n < 10000 → SIEVE(1)!n; n := n + 2]
||SIEVE(101)::*[n:integer;SIEVE(100)?n → print!n]
||print::*[(i:0..101) n:integer; SIEVE(i)?n → ...]
]

  沒有channel。能處理的素數的個數是在程式中指定的。

  40、Newsqueak

  circa 1988。

  Rob Pike語言設計,Tom Cargill和Doug McIlroy實現。

  使用了channels,這樣個數是可程式設計的。(channel這個idea從何而來?)

counter:=prog(end: int, c: chan of int)
{
    i:int;
    for(i=2; i<end; i++)
        c<-=i;
};

filter:=prog(prime: int, listen: chan of int, send: chan of int)
{
    i:int;
    for(;;)
        if((i=<-listen)%prime)
            send<-=i;
};

sieve:=prog(c: chan of int)
{
    for(;;){
        prime:=<-c;
        print(prime, " ");
        newc:=mk(chan of int);
        begin filter(prime, c, newc);
        c=newc;
    }
};

count:=mk(chan of int);

begin counter(10000, count);
begin sieve(count);
"";

  41、sieve.go,2008年3月5日

  使用go規範編寫的第一個版本,可能是第二個由go編寫的重要程式。

  >用於傳送;<用於接收。Channel是指標。Main是頭字母大寫的。

package Main

// Send the sequence 2, 3, 4, … to channel 'ch'.
func Generate(ch *chan> int) {
    for i := 2; ; i++ {
        >ch = i;    // Send 'i' to channel 'ch'.
    }
}

// Copy the values from channel 'in' to channel 'out',
// removing those divisible by 'prime'.
func Filter(in *chan< int, out *chan> int, prime int) {
    for ; ; {
        i := <in;    // Receive value of new variable 'i' from 'in'.
        if i % prime != 0 {
            >out = i;    // Send 'i' to channel 'out'.
        }
    }
}

// The prime sieve: Daisy-chain Filter processes together.
func Sieve() {
    ch := new(chan int);  // Create a new channel.
    go Generate(ch);      // Start Generate() as a subprocess.
    for ; ; {
        prime := <ch;
        printf("%d\n", prime);
        ch1 := new(chan int);
        go Filter(ch, ch1, prime);
        ch = ch1;
    }
}

func Main() {
    Sieve();
}

  42. sieve.go,2008年7月22日

  -<用於傳送;-<用於接收。Channel仍然是指標。但現在main不是大寫字母開頭的了。

package main

// Send the sequence 2, 3, 4, … to channel 'ch'.
func Generate(ch *chan-< int) {
    for i := 2; ; i++ {
        ch -< i    // Send 'i' to channel 'ch'.
    }
}

// Copy the values from channel 'in' to channel 'out',
// removing those divisible by 'prime'.
func Filter(in *chan<- int, out *chan-< int, prime int) {
    for {
        i := <-in;    // Receive value of new variable 'i' from 'in'.
        if i % prime != 0 {
            out -< i    // Send 'i' to channel 'out'.
        }
    }
}

// The prime sieve: Daisy-chain Filter processes together.
func Sieve() {
    ch := new(chan int);  // Create a new channel.
    go Generate(ch);      // Start Generate() as a subprocess.
    for {
        prime := <-ch;
        printf("%d\n",    prime);
        ch1 := new(chan int);
        go Filter(ch, ch1, prime);
        ch = ch1
    }
}

func main() {
    Sieve()
}

  43、sieve.go,2008年9月17日

  通訊操作符現在是<-。channel仍然是指標。

package main

// Send the sequence 2, 3, 4, … to channel 'ch'.
func Generate(ch *chan <- int) {
    for i := 2; ; i++ {
        ch <- i  // Send 'i' to channel 'ch'.
    }
}

// Copy the values from channel 'in' to channel 'out',
// removing those divisible by 'prime'.
func Filter(in *chan <- int, out *<-chan int, prime int) {
    for {
        i := <-in;  // Receive value of new variable 'i' from 'in'.
        if i % prime != 0 {
            out <- i  // Send 'i' to channel 'out'.
        }
    }
}

// The prime sieve: Daisy-chain Filter processes together.
func Sieve() {
    ch := new(chan int);  // Create a new channel.
    go Generate(ch);      // Start Generate() as a subprocess.
    for {
        prime := <-ch;
        print(prime, "\n");
        ch1 := new(chan int);
        go Filter(ch, ch1, prime);
        ch = ch1
    }
}

func main() {
    Sieve()
}

  44、sieve.go,2009年1月6日

 引入了make內建操作符。沒有指標。編碼錯誤!(有個*被留下了,錯誤的引數型別)

package main

// Send the sequence 2, 3, 4, … to channel 'ch'.
func Generate(ch chan <- int) {
    for i := 2; ; i++ {
        ch <- i  // Send 'i' to channel 'ch'.
    }
}

// Copy the values from channel 'in' to channel 'out',
// removing those divisible by 'prime'.
func Filter(in chan <- int, out *<-chan int, prime int) {
    for {
        i := <-in;  // Receive value of new variable 'i' from 'in'.
        if i % prime != 0 {
            out <- i  // Send 'i' to channel 'out'.
        }
    }
}

// The prime sieve: Daisy-chain Filter processes together.
func Sieve() {
    ch := make(chan int);  // Create a new channel.
    go Generate(ch);       // Start Generate() as a subprocess.
    for {
        prime := <-ch;
        print(prime, "\n");
        ch1 := make(chan int);
        go Filter(ch, ch1, prime);
        ch = ch1
    }
}

func main() {
    Sieve()
}

  45、sieve.go,2009年9月25日

  第一個正確的現代版本。同樣,大寫頭母不見了,使用了fmt。

package main

import "fmt"

// Send the sequence 2, 3, 4, … to channel 'ch'.
func generate(ch chan<- int) {
    for i := 2; ; i++ {
        ch <- i;    // Send 'i' to channel 'ch'.
    }
}

// Copy the values from channel 'in' to channel 'out',
// removing those divisible by 'prime'.
func filter(src <-chan int, dst chan<- int, prime int) {
    for i := range src {    // Loop over values received from 'src'.
        if i%prime != 0 {
            dst <- i;    // Send 'i' to channel 'dst'.
        }
    }
}

// The prime sieve: Daisy-chain filter processes together.
func sieve() {
    ch := make(chan int);  // Create a new channel.
    go generate(ch);       // Start generate() as a subprocess.
    for {
        prime := <-ch;
        fmt.Print(prime, "\n");
        ch1 := make(chan int);
        go filter(ch, ch1, prime);
        ch = ch1;
    }
}

func main() {
    sieve();
}

  46、sieve.go,2009年12月10日

  分號不見了。程式已經與現在一致了。

package main

import "fmt"

// Send the sequence 2, 3, 4, … to channel 'ch'.
func generate(ch chan<- int) {
    for i := 2; ; i++ {
        ch <- i  // Send 'i' to channel 'ch'.
    }
}

// Copy the values from channel 'src' to channel 'dst',
// removing those divisible by 'prime'.
func filter(src <-chan int, dst chan<- int, prime int) {
    for i := range src {  // Loop over values received from 'src'.
        if i%prime != 0 {
            dst <- i  // Send 'i' to channel 'dst'.
        }
    }
}

// The prime sieve: Daisy-chain filter processes together.
func sieve() {
    ch := make(chan int)  // Create a new channel.
    go generate(ch)       // Start generate() as a subprocess.
    for {
        prime := <-ch
        fmt.Print(prime, "\n")
        ch1 := make(chan int)
        go filter(ch, ch1, prime)
        ch = ch1
    }
}

func main() {
    sieve()
}

  這個優美的方案來自於幾十年的設計過程。

  47、旁邊,沒有討論到的

  select

  真實併發程式的核心聯結器(connector)

  最初起源於Dijkstra的守衛命令(guarded command)

  在Hoare的CSP理論實現真正併發。

  經過Newsqueak、Alef、Limbo和其他語言改良後

  2008年3月26日出現在Go版本中。

  簡單,澄清,語法方面的考慮。

  48、穩定性

  Sieve程式自從2009年末就再未改變過。– 穩定!

  開源系統並不總是相容和穩定的。

  但,Go是。(相容和穩定的)

  這是Go成功的一個重要原因。

  49、趨勢

  圖資料展示了Go 1.0釋出後Go語言的爆發。

  50、成功

  Go成功的元素:

  顯然的:功能和工具。

  * 併發
  * 垃圾回收
  * 高效的實現
  * 給人以動態型別體驗的靜態型別系統
  * 豐富但規模有限的標準庫
  * 工具化
  * gofmt
  * 在大規模系統中的應用

  不那麼顯然的:過程

  * 始終聚焦最初的目標
  * 在凍結後的集中開發
  * 小核心團隊易於取得一致
  * 社群的重要貢獻
  * 豐富的生態系統

  總之,開源社群共享了我們的使命,聚焦於為當今的世界設計一門語言。

相關文章