Brad Fitzpatrick dan saya (Andrew Gerrand) baru-baru ini mulai menata ulang godoc, dan saya baru sadar, godoc adalah salah satu program Go yang paling lama. Robert Griesemer menulis program tersebut di awal 2009, dan kita masih menggunakannya sampai sekarang.

Saat saya men-tweet tentang hal ini, Dave Cheney membalas dengan pertanyaan menarik: program Go apa yang paling tua? Rob Pike mencari-cari di dalam surel-nya dan menemukan sebuah pesan lama dari Ken Thomposon ke Robert (Griesemer).

Yang kita lihat selanjutnya adalah program Go yang pertama. Yang ditulis oleh Rob di bulan Februari 2008, saat tim Go saat itu hanya Rob, Robert, dan Ken. Mereka memiliki sejumlah fitur yang solid (yang ditulis dalam blog ini dan spesifikasi bahasa yang agak kasar. Ken baru saja menyelesaikan versi pertama dari kompilator Go (yang belum menghasilkan kode natif, tapi transliterasi kode Go ke C untuk prototipe pertama) dan saatnya mencoba membuat program dengan kompilator tersebut.

Rob mengirim surel ke "Tim 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.

(Baris icounter pada keluaran program adalah jumlah perintah yang dieksekusi, ditulis untuk pemeriksaan.)

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();
}

Program tersebut memindai dan mencetak S-expression. Ia tidak membaca input dari user dan tidak memiliki impor, bergantung kepada fasilitas print untuk pencetakan. Ia ditulis benar-benar di hari pertama sejak kompilator bekerja walau belum komplit. Kebanyakan fitur bahasa belum diimplementasikan dan beberapa darinya bahkan belum ada spesifikasi-nya.

Namun, bentuk awal dari bahasa yang kita pakai sekarang sudah tampak pada program tersebut. Deklarasi tipe dan variabel, alur kontrol, dan perintah paket tidak banyak berubah.

Tapi banyak perbedaan dan kekurangan. Yang paling mencolok yaitu tidak adanya konkurensi dan interface—keduanya dianggap fitur esensial sejak hari pertama namun belum dirancang.

Kata func adalah sebuah fungsi, dan bentuknya menspesifikasikan nilai kembalian sebelum argumen, dipisahkan oleh <-, yang sekarang kita gunakan sebagai operator pengiriman/penerimaan dari dan ke channel. Misalnya, fungsi WhiteSpace menerima integer c dan mengembalikan sebuah boolean.

function WhiteSpace(bool <- c int)

Tanda panah tersebut adalah pemisah sebelum sintaksis yang lebih bagus dirancang untuk mendeklarasikan banyak nilai kembalian.

Method ditulis berbeda dari fungsi dan punya sintaksis-nya sendiri.

method (this *Slist) Car(*Slist <-) {
	return this.list.car;
}

Dan method dideklarasikan di dalam definisi struct, walaupun hal ini nantinya berubah.

type Slist struct {
	...
	Car method(*Slist <-);
}

Belum ada tipe string, walaupun spesifikasi-nya sudah ada. Untuk bekerja dengan string, Rob harus membuat string sebagai array bertipe uint8 dengan konstruksi yang aneh. (Tipe array tidak berubah dan slice belum ada rancangannya, walaupun ada konsep "open array" yang belum diimplementasikan.)

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;
...

Fungsi panic dan print adalah bawaan, bukan fungsi yang dideklarasikan.

print "parse error: expected ", c, "\n";
panic "parse";

Dan masih ada banyak beberapa perbedaan kecil lainnya; lihat apakah Anda bisa mengidentifikasi diantaranya.

Kurang dari dua tahun setelah program tersebut ditulis, Go dirilis sebagai proyek sumber terbuka. Melihat ke belakang, sangatlah jelas betapa bahasa Go telah berkembang dan dewasa. (Hal terakhir yang berubah antara proto-Go dan Go yang kita kenal sekarang yaitu dihapusnya titik-koma.)

Namun yang paling mencolok adalah betapa banyak yang telah kita pelajari tentang menulis kode Go. Misalnya, Rob menulis penerima method dengan this, namun sekarang kita menggunakan nama yang singkat dengan konteks yang spesifik. Ada ratusan lebih contoh-contoh penting lainnya dan sampai sekarang kita masih mencari cara terbaik menulis kode Go. (Perhatikan trik cerdik dari paket glog untuk menangani tingkat verbositas.)

Saya ingin tahu apa yang akan kita pelajari di masa depan.