Goブログ
最初のGoプログラム
ブラッド・フィッツパトリックと私(アンドリュー・ゲランド)は最近godocの再構築を始め、それが最も古いGoプログラムの1つであることに気づきました。ロバート・グリーズマーは2009年初頭に書き始め、今日でも使用しています。
私がこれについてツイートしたところ、デイブ・チェニーが興味深い質問で返信しました。最も古いGoプログラムは何ですか?ロブ・パイクは彼のメールを掘り下げ、ロバートとケン・トンプソンへの古いメッセージの中でそれを見つけました。
以下は最初のGoプログラムです。それはロブが2008年2月に、チームがロブ、ロバート、ケンの3人だけだったときに書かれました。彼らは堅牢な機能リスト(このブログ投稿で言及)と大まかな言語仕様を持っていました。ケンはGoコンパイラの最初の動作バージョンを完成させたばかりでした(ネイティブコードを生成するのではなく、GoコードをCに変換して迅速なプロトタイピングを行いました)そして、それを用いてプログラムを書いてみる時が来ました。
ロブは「Goチーム」にメールを送信しました。
From: Rob 'Commander' Pike
Date: Wed, Feb 6, 2008 at 3:42 PM
To: Ken Thompson, Robert Griesemer
Subject: slist
it works now.
roro=% a.out
(defn foo (add 12 34))
return: icounter = 4440
roro=%
here's the code.
some ugly hackery to get around the lack of strings.
(プログラム出力の`icounter`行は、デバッグのために出力される実行されたステートメントの数です。)
package main // fake stuff type char uint8; // const char TESTSTRING[] = "(defn foo (add 'a 'b))\n"; type Atom struct { string *[100]char; integer int; next *Slist; /* in hash bucket */ } type List struct { car *Slist; cdr *Slist; } type Slist struct { isatom bool; isstring bool; //union { atom Atom; list List; //} u; Free method(); Print method(); PrintOne method(doparen bool); String method(*char <-); Integer method(int <-); Car method(*Slist <-); Cdr method(*Slist <-); } method (this *Slist) Car(*Slist <-) { return this.list.car; } method (this *Slist) Cdr(*Slist <-) { return this.list.cdr; } method (this *Slist) String(*[100]char <-) { return this.atom.string; } method (this *Slist) Integer(int <-) { return this.atom.integer; } function OpenFile(); function Parse(*Slist <-); //Slist* atom(char *s, int i); var token int; var peekc int = -1; var lineno int32 = 1; var input [100*1000]char; var inputindex int = 0; var tokenbuf [100]char; var EOF int = -1; // BUG should be const function main(int32 <-) { var list *Slist; OpenFile(); for ;; { list = Parse(); if list == nil { break; } list.Print(); list.Free(); break; } return 0; } method (slist *Slist) Free(<-) { if slist == nil { return; } if slist.isatom { // free(slist.String()); } else { slist.Car().Free(); slist.Cdr().Free(); } // free(slist); } method (slist *Slist) PrintOne(<- doparen bool) { if slist == nil { return; } if slist.isatom { if slist.isstring { print(slist.String()); } else { print(slist.Integer()); } } else { if doparen { print("("); } slist.Car().PrintOne(true); if slist.Cdr() != nil { print(" "); slist.Cdr().PrintOne(false); } if doparen { print(")"); } } } method (slist *Slist) Print() { slist.PrintOne(true); print "\n"; } function Get(int <-) { var c int; if peekc >= 0 { c = peekc; peekc = -1; } else { c = convert(int, input[inputindex]); inputindex = inputindex + 1; // BUG should be incr one expr if c == '\n' { lineno = lineno + 1; } if c == '\0' { inputindex = inputindex - 1; c = EOF; } } return c; } function WhiteSpace(bool <- c int) { return c == ' ' || c == '\t' || c == '\r' || c == '\n'; } function NextToken() { var i, c int; var backslash bool; tokenbuf[0] = '\0'; // clear previous token c = Get(); while WhiteSpace(c) { c = Get(); } switch c { case EOF: token = EOF; case '(': case ')': token = c; break; case: for i = 0; i < 100 - 1; { // sizeof tokenbuf - 1 tokenbuf[i] = convert(char, c); i = i + 1; c = Get(); if c == EOF { break; } if WhiteSpace(c) || c == ')' { peekc = c; break; } } if i >= 100 - 1 { // sizeof tokenbuf - 1 panic "atom too long\n"; } tokenbuf[i] = '\0'; if '0' <= tokenbuf[0] && tokenbuf[0] <= '9' { token = '0'; } else { token = 'A'; } } } function Expect(<- c int) { if token != c { print "parse error: expected ", c, "\n"; panic "parse"; } NextToken(); } // Parse a non-parenthesized list up to a closing paren or EOF function ParseList(*Slist <-) { var slist, retval *Slist; slist = new(Slist); slist.list.car = nil; slist.list.cdr = nil; slist.isatom = false; slist.isstring = false; retval = slist; for ;; { slist.list.car = Parse(); if token == ')' { // empty cdr break; } if token == EOF { // empty cdr BUG SHOULD USE || break; } slist.list.cdr = new(Slist); slist = slist.list.cdr; } return retval; } function atom(*Slist <- i int) { // BUG: uses tokenbuf; should take argument var h, length int; var slist, tail *Slist; slist = new(Slist); if token == '0' { slist.atom.integer = i; slist.isstring = false; } else { slist.atom.string = new([100]char); var i int; for i = 0; ; i = i + 1 { (*slist.atom.string)[i] = tokenbuf[i]; if tokenbuf[i] == '\0' { break; } } //slist.atom.string = "hello"; // BUG! s; //= strdup(s); slist.isstring = true; } slist.isatom = true; return slist; } function atoi(int <-) { // BUG: uses tokenbuf; should take argument var v int = 0; for i := 0; '0' <= tokenbuf[i] && tokenbuf[i] <= '9'; i = i + 1 { v = 10 * v + convert(int, tokenbuf[i] - '0'); } return v; } function Parse(*Slist <-) { var slist *Slist; if token == EOF || token == ')' { return nil; } if token == '(' { NextToken(); slist = ParseList(); Expect(')'); return slist; } else { // Atom switch token { case EOF: return nil; case '0': slist = atom(atoi()); case '"': case 'A': slist = atom(0); case: slist = nil; print "unknown token"; //, token, tokenbuf; } NextToken(); return slist; } return nil; } function OpenFile() { //strcpy(input, TESTSTRING); //inputindex = 0; // (defn foo (add 12 34))\n inputindex = 0; peekc = -1; // BUG EOF = -1; // BUG i := 0; input[i] = '('; i = i + 1; input[i] = 'd'; i = i + 1; input[i] = 'e'; i = i + 1; input[i] = 'f'; i = i + 1; input[i] = 'n'; i = i + 1; input[i] = ' '; i = i + 1; input[i] = 'f'; i = i + 1; input[i] = 'o'; i = i + 1; input[i] = 'o'; i = i + 1; input[i] = ' '; i = i + 1; input[i] = '('; i = i + 1; input[i] = 'a'; i = i + 1; input[i] = 'd'; i = i + 1; input[i] = 'd'; i = i + 1; input[i] = ' '; i = i + 1; input[i] = '1'; i = i + 1; input[i] = '2'; i = i + 1; input[i] = ' '; i = i + 1; input[i] = '3'; i = i + 1; input[i] = '4'; i = i + 1; input[i] = ')'; i = i + 1; input[i] = ')'; i = i + 1; input[i] = '\n'; i = i + 1; NextToken(); }
このプログラムはS式を解析して出力します。ユーザー入力を受け取らず、インポートもありません。出力にはビルトインの`print`機能のみに依存しています。動作するが基本的なコンパイラがあったまさに最初の日に書かれました。言語の多くの部分は実装されておらず、その一部はまだ指定されていませんでした。
それでも、今日の言語の基本的な特徴はこのプログラムで認識できます。型と変数の宣言、制御フロー、パッケージステートメントはそれほど変わっていません。
しかし、多くの違いと欠点があります。最も重要なのは、並行処理とインターフェースの欠如です。どちらも初日から必須と見なされていましたが、まだ設計されていませんでした。
`func`は`function`であり、そのシグネチャは引数の前に戻り値を指定し、`<-`で区切っていました。これは現在、チャネルの送受信演算子として使用しています。たとえば、`WhiteSpace`関数は整数`c`を受け取り、ブール値を返します。
function WhiteSpace(bool <- c int)
この矢印は、複数の戻り値を宣言するためのより良い構文が登場するまでの繋ぎの手段でした。
メソッドは関数とは異なり、独自のキーワードを持っていました。
method (this *Slist) Car(*Slist <-) {
return this.list.car;
}
メソッドは構造体定義で事前に宣言されていましたが、すぐに変更されました。
type Slist struct {
...
Car method(*Slist <-);
}
文字列はありませんでしたが、仕様には含まれていました。これを回避するために、ロブはぎこちない構成で`uint8`配列として入力文字列を構築する必要がありました。(配列は基本的なものであり、スライスはまだ設計されておらず、実装されていませんでしたが、「オープン配列」という未実装の概念がありました。)
input[i] = '('; i = i + 1;
input[i] = 'd'; i = i + 1;
input[i] = 'e'; i = i + 1;
input[i] = 'f'; i = i + 1;
input[i] = 'n'; i = i + 1;
input[i] = ' '; i = i + 1;
...
`panic`と`print`はどちらもビルトインキーワードであり、事前に宣言された関数ではありませんでした。
print "parse error: expected ", c, "\n";
panic "parse";
そして、他にも多くの小さな違いがあります。他の違いを見つけてみてください。
このプログラムが書かれてから2年も経たないうちに、Goはオープンソースプロジェクトとしてリリースされました。振り返ってみると、言語がどれだけ成長し、成熟したかが印象的です。(このプロトGoと今日のGoの間に最後に変更されたのは、セミコロンの削除です。)
しかし、さらに印象的なのは、Goコードの書き方についてどれだけ学んだかということです。たとえば、ロブはメソッドレシーバを`this`と呼びましたが、現在はより短いコンテキスト固有の名前を使用しています。さらに重要な例が何百もあります。そして今日でも、Goコードを記述するためのより良い方法を依然として発見しています。(glogパッケージの冗長レベルの処理に関する巧妙なトリックをチェックしてください。)
明日は何が分かるか楽しみです。